Skip to content

Commit

Permalink
Factoring out the dependency graph classes.
Browse files Browse the repository at this point in the history
  • Loading branch information
johnbywater committed Sep 13, 2017
1 parent fe9ce49 commit 80cd6f2
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 158 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ and running your program.
Let's jump in at the deep-end with a simple model of a gas-fired power station.

```python
quantdsl_module = """
source_code = """
PowerStation(Date('2012-01-01'), Date('2012-01-13'), Market('GAS'), Market('POWER'), Stopped(1))
def PowerStation(start, end, gas, power, duration_off):
Expand Down Expand Up @@ -131,7 +131,7 @@ app = QuantDslApplicationWithPythonObjects()
Compile the module into a dependency graph.

```python
dependency_graph = app.compile(quantdsl_module)
contract_specification = app.compile(source_code)
```

Calibrate from historical data. In this example, we can just register some calibration parameters.
Expand Down Expand Up @@ -159,7 +159,7 @@ Make a simulation from the calibration.
import datetime

market_simulation = app.simulate(
dependency_graph,
contract_specification,
market_calibration,
path_count=20000,
observation_date=datetime.datetime(2011, 1, 1)
Expand All @@ -169,7 +169,7 @@ market_simulation = app.simulate(
Make an evaluation using the simulation.

```python
valuation = app.evaluate(dependency_graph, market_simulation)
valuation = app.evaluate(contract_specification, market_simulation)
```

Inspect the estimated value.
Expand Down
10 changes: 4 additions & 6 deletions quantdsl/application/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,9 @@ def identify_simulation_requirements(self, contract_specification, observation_d
observation_date,
requirements)

def start_contract_valuation(self, entity_id, dependency_graph_id, market_simulation):
assert isinstance(dependency_graph_id, six.string_types), dependency_graph_id
assert isinstance(market_simulation, MarketSimulation)
return start_contract_valuation(entity_id, dependency_graph_id, market_simulation.id)
def start_contract_valuation(self, contract_specification_id, market_simulation_id):
assert isinstance(contract_specification_id, six.string_types), contract_specification_id
return start_contract_valuation(contract_specification_id, market_simulation_id)

def loop_on_evaluation_queue(self, call_result_lock, compute_pool=None, result_counters=None, usage_counters=None):
loop_on_evaluation_queue(
Expand Down Expand Up @@ -229,8 +228,7 @@ def simulate(self, contract_specification, market_calibration, observation_date,
return market_simulation

def evaluate(self, contract_specification, market_simulation):
contract_valuation_id = create_contract_valuation_id()
return self.start_contract_valuation(contract_valuation_id, contract_specification.id, market_simulation)
return self.start_contract_valuation(contract_specification.id, market_simulation.id)

def get_result(self, contract_valuation):
call_result_id = make_call_result_id(contract_valuation.id, contract_valuation.dependency_graph_id)
Expand Down
7 changes: 4 additions & 3 deletions quantdsl/domain/model/contract_valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ def dependency_graph_id(self):
return self._dependency_graph_id


def start_contract_valuation(entity_id, dependency_graph_id, market_simulation_id):
def start_contract_valuation(contract_specification_id, market_simulation_id):
contract_valuation_id = create_contract_valuation_id()
contract_valuation_created = ContractValuation.Created(
entity_id=entity_id,
entity_id=contract_valuation_id,
market_simulation_id=market_simulation_id,
dependency_graph_id=dependency_graph_id,
dependency_graph_id=contract_specification_id,
)
contract_valuation = ContractValuation.mutator(event=contract_valuation_created)
publish(contract_valuation_created)
Expand Down
9 changes: 3 additions & 6 deletions quantdsl/domain/services/call_links.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from quantdsl.domain.model.call_link import CallLinkRepository, CallLink


def regenerate_execution_order(dependency_graph_id, call_link_repo):
def regenerate_execution_order(contract_specification_id, call_link_repo):
# assert isinstance(call_link_repo, CallLinkRepository)
link_id = dependency_graph_id
link_id = contract_specification_id
while True:
call_id = get_next_call_id(call_link_repo, link_id)
if call_id is None:
break
yield call_id
if call_id == dependency_graph_id:
if call_id == contract_specification_id:
break
link_id = call_id

Expand Down
8 changes: 4 additions & 4 deletions quantdsl/domain/services/contract_valuations.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ def evaluate_contract_in_series(contract_valuation_id, contract_valuation_repo,
contract_valuation = contract_valuation_repo[contract_valuation_id]
assert isinstance(contract_valuation, ContractValuation), contract_valuation

# Get the dependency graph ID.
dependency_graph_id = contract_valuation.dependency_graph_id
# Get the contract specification ID.
contract_specification_id = contract_valuation.dependency_graph_id

# Get the market simulation.
market_simulation = market_simulation_repo[contract_valuation.market_simulation_id]

# Follow the execution order...
for call_id in regenerate_execution_order(dependency_graph_id, call_link_repo):
for call_id in regenerate_execution_order(contract_specification_id, call_link_repo):

# Get the call requirement entity.
call_requirement = call_requirement_repo[call_id]
Expand Down Expand Up @@ -102,7 +102,7 @@ def evaluate_contract_in_series(contract_valuation_id, contract_valuation_repo,
result_value=result_value,
perturbed_values=perturbed_values,
contract_valuation_id=contract_valuation_id,
dependency_graph_id=dependency_graph_id,
dependency_graph_id=contract_specification_id,
)

# # Check for results that should be deleted.
Expand Down
250 changes: 125 additions & 125 deletions quantdsl/infrastructure/runners/base.py
Original file line number Diff line number Diff line change
@@ -1,125 +1,125 @@
from abc import ABCMeta, abstractmethod

import six

from quantdsl.domain.model.call_specification import CallSpecification
from quantdsl.domain.model.dependency_graph import DependencyGraph
from quantdsl.exceptions import DslSyntaxError, DslSystemError
from quantdsl.semantics import Module, DslNamespace, DslExpression, compile_dsl_module, list_fixing_dates


class DependencyGraphRunner(six.with_metaclass(ABCMeta)):

def __init__(self, dependency_graph):
assert isinstance(dependency_graph, DependencyGraph)
self.dependency_graph = dependency_graph

def evaluate(self, **kwds):
self.run(**kwds)
try:
return self.results_repo[self.dependency_graph.root_stub_id]
except KeyError:
raise DslSystemError("Result not found for root stub ID '{}'.".format(
self.dependency_graph.root_stub_id
))

@abstractmethod
def run(self, **kwargs):
self.run_kwds = kwargs
self.call_count = 0
self.results_repo = {}
self.dependencies = {}

def get_evaluation_kwds(self, dsl_source, effective_present_time):
evaluation_kwds = self.run_kwds.copy()

from quantdsl.services import list_fixing_dates
from quantdsl.domain.services.parser import dsl_parse
stubbed_module = dsl_parse(dsl_source)
assert isinstance(stubbed_module, Module)
fixing_dates = list_fixing_dates(stubbed_module)
if effective_present_time is not None:
fixing_dates.append(effective_present_time)

# Rebuild the data structure (there was a problem, but I can't remember what it was.
# Todo: Try without this block, perhaps the problem doesn't exist anymore.
if 'all_market_prices' in evaluation_kwds:
all_market_prices = evaluation_kwds.pop('all_market_prices')
evaluation_kwds['all_market_prices'] = dict()
for market_name in all_market_prices.keys():
# if market_name not in market_names:
# continue
market_prices = dict()
for date in fixing_dates:
market_prices[date] = all_market_prices[market_name][date]
evaluation_kwds['all_market_prices'][market_name] = market_prices
return evaluation_kwds


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)


def handle_result(call_requirement_id, result_value, results, dependents, dependencies, execution_queue):

# Set the results.
results[call_requirement_id] = result_value

# Check if dependents are ready to be executed.
for dependent_id in dependents[call_requirement_id]:
if dependent_id in results:
continue
subscriber_required_ids = dependencies[dependent_id]
# It's ready unless it requires a call that doesn't have a result yet.
for required_id in subscriber_required_ids:
# - don't need to see if this call has a result, that's why we're here!
if required_id != call_requirement_id:
# - check if the required call already has a result
if required_id not in results:
break
else:
# All required results exist for the dependent call.
execution_queue.put(dependent_id)

# Check for results that should be deleted.
# - dependency results should be deleted if there is a result for each dependent of the dependency
for dependency_id in dependencies[call_requirement_id]:
for dependent_id in dependents[dependency_id]:
if dependent_id != call_requirement_id and dependent_id not in results:
# Need to keep it.
break
else:
del(results[dependency_id])
# from abc import ABCMeta, abstractmethod
#
# import six
#
# from quantdsl.domain.model.call_specification import CallSpecification
# from quantdsl.domain.model.dependency_graph import DependencyGraph
# from quantdsl.exceptions import DslSyntaxError, DslSystemError
# from quantdsl.semantics import Module, DslNamespace, DslExpression, compile_dsl_module, list_fixing_dates


# class DependencyGraphRunner(six.with_metaclass(ABCMeta)):
#
# def __init__(self, dependency_graph):
# assert isinstance(dependency_graph, DependencyGraph)
# self.dependency_graph = dependency_graph
#
# def evaluate(self, **kwds):
# self.run(**kwds)
# try:
# return self.results_repo[self.dependency_graph.root_stub_id]
# except KeyError:
# raise DslSystemError("Result not found for root stub ID '{}'.".format(
# self.dependency_graph.root_stub_id
# ))
#
# @abstractmethod
# def run(self, **kwargs):
# self.run_kwds = kwargs
# self.call_count = 0
# self.results_repo = {}
# self.dependencies = {}
#
# def get_evaluation_kwds(self, dsl_source, effective_present_time):
# evaluation_kwds = self.run_kwds.copy()
#
# from quantdsl.services import list_fixing_dates
# from quantdsl.domain.services.parser import dsl_parse
# stubbed_module = dsl_parse(dsl_source)
# assert isinstance(stubbed_module, Module)
# fixing_dates = list_fixing_dates(stubbed_module)
# if effective_present_time is not None:
# fixing_dates.append(effective_present_time)
#
# # Rebuild the data structure (there was a problem, but I can't remember what it was.
# # Todo: Try without this block, perhaps the problem doesn't exist anymore.
# if 'all_market_prices' in evaluation_kwds:
# all_market_prices = evaluation_kwds.pop('all_market_prices')
# evaluation_kwds['all_market_prices'] = dict()
# for market_name in all_market_prices.keys():
# # if market_name not in market_names:
# # continue
# market_prices = dict()
# for date in fixing_dates:
# market_prices[date] = all_market_prices[market_name][date]
# evaluation_kwds['all_market_prices'][market_name] = market_prices
# return evaluation_kwds


# 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)


# def handle_result(call_requirement_id, result_value, results, dependents, dependencies, execution_queue):
#
# # Set the results.
# results[call_requirement_id] = result_value
#
# # Check if dependents are ready to be executed.
# for dependent_id in dependents[call_requirement_id]:
# if dependent_id in results:
# continue
# subscriber_required_ids = dependencies[dependent_id]
# # It's ready unless it requires a call that doesn't have a result yet.
# for required_id in subscriber_required_ids:
# # - don't need to see if this call has a result, that's why we're here!
# if required_id != call_requirement_id:
# # - check if the required call already has a result
# if required_id not in results:
# break
# else:
# # All required results exist for the dependent call.
# execution_queue.put(dependent_id)
#
# # Check for results that should be deleted.
# # - dependency results should be deleted if there is a result for each dependent of the dependency
# for dependency_id in dependencies[call_requirement_id]:
# for dependent_id in dependents[dependency_id]:
# if dependent_id != call_requirement_id and dependent_id not in results:
# # Need to keep it.
# break
# else:
# del(results[dependency_id])
6 changes: 1 addition & 5 deletions quantdsl/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,7 @@ def dsl_eval(dsl_source, filename='<unknown>', is_parallel=None, dsl_classes=Non
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)
assert isinstance(dsl_expr, DslExpression), type(dsl_expr)

if is_verbose:
if isinstance(dsl_expr, DependencyGraph):
Expand Down

0 comments on commit 80cd6f2

Please sign in to comment.