diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000000000000000000000000000000000..246838cbda2c246a9ac3124fe28aceb8e751250a --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +exclude = build/*,venv/*,doc/source/*,tests/utils.py,braindump/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..a5b8250f00133f692cf2b43e6617e9d7acc6b124 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: c +cache: + directories: + - "$HOME/.cache" + - "$HOME/.ccache" +before_install: + - ccache -s + - export PATH=/usr/lib/ccache:${PATH} + - wget https://package-data.enthought.com/edm/rh5_x86_64/1.4/edm_1.4.1_linux_x86_64.sh && bash ./edm_1.4.1_linux_x86_64.sh -b -p $HOME + - export PATH=${HOME}/edm/bin:${PATH} + - edm environments create --version 3.5 force + - . $HOME/.edm/envs/force/bin/activate + - cat requirements/edm_requirements.txt | grep -v "^#" | while read line; do edm install -y `echo $line | awk '{print $1"=="$2}'`; done +install: + - pip install -r requirements/requirements.txt + - pip install -e . +script: + - pip install -r requirements/dev_requirements.txt + - flake8 . diff --git a/README.md b/README.md index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..68e6a408d871425da72d2320d1bf8ee666f18e57 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,8 @@ +FORCE BDSS +---------- + +This repository contains the implementation of the Business Decision System. It is implemented +under the FORCE project (Horizon 2020/NMBP-23-2016/721027). + + + diff --git a/braindump/main.py b/braindump/main.py index cbac2019f5f7dbf3f3277a66506c251f56e877f4..a61d6a00c4e4b9534c8375c5ecdf4fa738d95930 100644 --- a/braindump/main.py +++ b/braindump/main.py @@ -1,4 +1,4 @@ -from force import * +from force_bdss import * wf=Workflow() wf.set_mco(Dakota()) diff --git a/examples/test_workflow.json b/examples/test_workflow.json new file mode 100644 index 0000000000000000000000000000000000000000..1bbd3661e3c989d1f278022b08b73b7c639b0c41 --- /dev/null +++ b/examples/test_workflow.json @@ -0,0 +1,9 @@ +{ + "multi_criteria_optimization": { + "name": "basic" + }, + "key_performance_indicators": [ + "viscosity", "price" + ] +} + diff --git a/force/__init__.py b/force_bdss/__init__.py similarity index 100% rename from force/__init__.py rename to force_bdss/__init__.py diff --git a/force_bdss/bdss_application.py b/force_bdss/bdss_application.py new file mode 100644 index 0000000000000000000000000000000000000000..e6abb68250d434c03d1f88ff68fae5b9b6c91141 --- /dev/null +++ b/force_bdss/bdss_application.py @@ -0,0 +1,27 @@ +import json + +from envisage.api import Application +from traits.api import Unicode, Bool, Instance + +from force_bdss.workspecs.workflow import Workflow + + +class BDSSApplication(Application): + """Main application for the BDSS. + """ + id = "force_bdss.bdss_application" + + #: The path of the workflow file to open + workflow_filepath = Unicode() + + #: Deserialized content of the workflow file. + workflow = Instance(Workflow) + + #: This flags signals to the application not to execute and orchestrate + #: the MCO, but instead to perform a single evaluation under the + #: coordination of the MCO itself. See design notes for more details. + evaluate = Bool() + + def _workflow_default(self): + with open(self.workflow_filepath) as f: + return Workflow.from_json(json.load(f)) diff --git a/force_bdss/cli/force_bdss.py b/force_bdss/cli/force_bdss.py new file mode 100644 index 0000000000000000000000000000000000000000..37541e66f1d1132406392ab73c66da41d0779870 --- /dev/null +++ b/force_bdss/cli/force_bdss.py @@ -0,0 +1,30 @@ +import click +from envisage.core_plugin import CorePlugin + +from force_bdss.bdss_application import BDSSApplication +from force_bdss.core_mco_driver import CoreMCODriver +from force_bdss.kpi.key_performance_calculators_plugin import \ + KeyPerformanceCalculatorsPlugin +from force_bdss.mco.multi_criteria_optimizers_plugin import \ + MultiCriteriaOptimizersPlugin + + +@click.command() +@click.option("--evaluate", is_flag=True) +@click.argument('workflow_filepath', type=click.Path(exists=True)) +def run(evaluate, workflow_filepath): + + plugins = [ + CorePlugin(), + CoreMCODriver(), + KeyPerformanceCalculatorsPlugin(), + MultiCriteriaOptimizersPlugin(), + ] + + application = BDSSApplication( + plugins=plugins, + evaluate=evaluate, + workflow_filepath=workflow_filepath + ) + + application.run() diff --git a/force_bdss/core_mco_driver.py b/force_bdss/core_mco_driver.py new file mode 100644 index 0000000000000000000000000000000000000000..a0409df1f556745814ce739d2a7c8d1c1d0307c7 --- /dev/null +++ b/force_bdss/core_mco_driver.py @@ -0,0 +1,65 @@ +from envisage.extension_point import ExtensionPoint +from envisage.plugin import Plugin +from traits.has_traits import on_trait_change +from traits.trait_types import List + +from force_bdss.kpi.i_key_performance_calculator import ( + IKeyPerformanceCalculator) +from force_bdss.mco.i_multi_criteria_optimizers import IMultiCriteriaOptimizer + + +class CoreMCODriver(Plugin): + """Main plugin that handles the execution of the MCO + or the evaluation. + """ + + # Note: we are forced to declare these extensions points here instead + # of the application object, and this is why we have to use this plugin. + # It is a workaround to an envisage bug that does not find the extension + # points if declared on the application. + + #: A List of the available Multi Criteria Optimizers. + #: This will be populated by MCO plugins. + multi_criteria_optimizers = ExtensionPoint( + List(IMultiCriteriaOptimizer), + id='force_bdss.multi_criteria_optimizers') + + #: A list of the available Key Performance Indicator calculators. + #: It will be populated by plugins. + key_performance_calculators = ExtensionPoint( + List(IKeyPerformanceCalculator), + id='force_bdss.key_performance_calculators') + + @on_trait_change("application:started") + def application_started(self): + workflow = self.application.workflow + if self.application.evaluate: + for kpi in workflow.key_performance_indicators: + kpc = self._find_kpc_by_computes(kpi) + if kpc: + kpc.run(self.application) + else: + raise Exception("Requested KPI {} but don't know how" + "to compute it.".format(kpi)) + else: + mco_name = workflow.multi_criteria_optimization.name + mco = self._find_mco_by_name(mco_name) + if mco: + mco.run(self.application) + else: + raise Exception("Requested MCO {} but it's not available" + "to compute it.".format(mco_name)) + + def _find_kpc_by_computes(self, computes): + for kpc in self.key_performance_calculators: + if kpc.computes == computes: + return kpc + + return None + + def _find_mco_by_name(self, name): + for mco in self.multi_criteria_optimizers: + if mco.name == name: + return mco + + return None diff --git a/force_bdss/kpi/__init__.py b/force_bdss/kpi/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/kpi/basic.py b/force_bdss/kpi/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..4e0d021a3f10030fed5748ef11a1c8013149a158 --- /dev/null +++ b/force_bdss/kpi/basic.py @@ -0,0 +1,12 @@ +from traits.api import provides, HasStrictTraits, String + +from force_bdss.kpi.i_key_performance_calculator import ( + IKeyPerformanceCalculator) + + +@provides(IKeyPerformanceCalculator) +class Basic(HasStrictTraits): + computes = String("basic") + + def run(self, workflow): + print("Computing basic key performance indicator, {}".format(workflow)) diff --git a/force_bdss/kpi/i_key_performance_calculator.py b/force_bdss/kpi/i_key_performance_calculator.py new file mode 100644 index 0000000000000000000000000000000000000000..56b7205c466180837c4ad7b80a89393fc8f2cdb0 --- /dev/null +++ b/force_bdss/kpi/i_key_performance_calculator.py @@ -0,0 +1,8 @@ +from traits.api import Interface, String + + +class IKeyPerformanceCalculator(Interface): + computes = String() + + def run(self): + pass diff --git a/force_bdss/kpi/key_performance_calculators_plugin.py b/force_bdss/kpi/key_performance_calculators_plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..3a646d36d129d29cee7fbf3c7f403a844a4f4818 --- /dev/null +++ b/force_bdss/kpi/key_performance_calculators_plugin.py @@ -0,0 +1,22 @@ +from envisage.plugin import Plugin +from traits.api import List + +from force_bdss.kpi.i_key_performance_calculator import ( + IKeyPerformanceCalculator) + +from force_bdss.kpi.basic import Basic +from force_bdss.kpi.price import Price +from force_bdss.kpi.viscosity import Viscosity + + +class KeyPerformanceCalculatorsPlugin(Plugin): + + id = "force_bdss.key_performance_calculators_plugin" + + key_performance_calculators = List( + IKeyPerformanceCalculator, + contributes_to='force_bdss.key_performance_calculators' + ) + + def _key_performance_calculators_default(self): + return [Basic(), Viscosity(), Price()] diff --git a/force_bdss/kpi/price.py b/force_bdss/kpi/price.py new file mode 100644 index 0000000000000000000000000000000000000000..79112547824c97c0eb08954c26feddb13fccd5ea --- /dev/null +++ b/force_bdss/kpi/price.py @@ -0,0 +1,12 @@ +from traits.api import provides, HasStrictTraits, String + +from force_bdss.kpi.i_key_performance_calculator import ( + IKeyPerformanceCalculator) + + +@provides(IKeyPerformanceCalculator) +class Price(HasStrictTraits): + computes = String("price") + + def run(self, workflow): + print("Computing price") diff --git a/force_bdss/kpi/viscosity.py b/force_bdss/kpi/viscosity.py new file mode 100644 index 0000000000000000000000000000000000000000..d8c3f3eac9214211903a6746b5a7884aa489ff54 --- /dev/null +++ b/force_bdss/kpi/viscosity.py @@ -0,0 +1,12 @@ +from traits.api import provides, HasStrictTraits, String + +from force_bdss.kpi.i_key_performance_calculator import ( + IKeyPerformanceCalculator) + + +@provides(IKeyPerformanceCalculator) +class Viscosity(HasStrictTraits): + computes = String("viscosity") + + def run(self, workflow): + print("Computing viscosity") diff --git a/force_bdss/mco/__init__.py b/force_bdss/mco/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/force_bdss/mco/basic.py b/force_bdss/mco/basic.py new file mode 100644 index 0000000000000000000000000000000000000000..2e29c3bc233236dd41de6edad6f3e605c372b893 --- /dev/null +++ b/force_bdss/mco/basic.py @@ -0,0 +1,16 @@ +import subprocess +import sys + +from traits.api import provides, HasStrictTraits, String + +from force_bdss.mco.i_multi_criteria_optimizers import IMultiCriteriaOptimizer + + +@provides(IMultiCriteriaOptimizer) +class Basic(HasStrictTraits): + name = String("basic") + + def run(self, application): + print("Running Basic optimizer") + subprocess.check_call([sys.argv[0], "--evaluate", + application.workflow_filepath]) diff --git a/force_bdss/mco/dakota.py b/force_bdss/mco/dakota.py new file mode 100644 index 0000000000000000000000000000000000000000..b046b11f3cd407015593c92dc6e187695bee9b39 --- /dev/null +++ b/force_bdss/mco/dakota.py @@ -0,0 +1,17 @@ +import subprocess + +import sys + +from traits.api import provides, HasStrictTraits, String + +from force_bdss.mco.i_multi_criteria_optimizers import IMultiCriteriaOptimizer + + +@provides(IMultiCriteriaOptimizer) +class Dakota(HasStrictTraits): + name = String("dakota") + + def run(self, application): + print("Running dakota optimizer") + subprocess.check_call([sys.argv[0], "--evaluate", + application.workflow_filepath]) diff --git a/force_bdss/mco/i_multi_criteria_optimizers.py b/force_bdss/mco/i_multi_criteria_optimizers.py new file mode 100644 index 0000000000000000000000000000000000000000..93c6b25dca4f73afc08caa0d348c59b9a2e195eb --- /dev/null +++ b/force_bdss/mco/i_multi_criteria_optimizers.py @@ -0,0 +1,8 @@ +from traits.api import Interface, String + + +class IMultiCriteriaOptimizer(Interface): + name = String() + + def run(self, application): + pass diff --git a/force_bdss/mco/multi_criteria_optimizers_plugin.py b/force_bdss/mco/multi_criteria_optimizers_plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..79e3f3066fc1d589543de3ac384d221c2b7b0dba --- /dev/null +++ b/force_bdss/mco/multi_criteria_optimizers_plugin.py @@ -0,0 +1,18 @@ +from envisage.plugin import Plugin +from traits.api import List + +from force_bdss.mco.basic import Basic +from force_bdss.mco.dakota import Dakota +from force_bdss.mco.i_multi_criteria_optimizers import IMultiCriteriaOptimizer + + +class MultiCriteriaOptimizersPlugin(Plugin): + id = "force_bdss.multi_criteria_optimizers_plugin" + + multi_criteria_optimizers = List( + IMultiCriteriaOptimizer, + contributes_to='force_bdss.multi_criteria_optimizers' + ) + + def _multi_criteria_optimizers_default(self): + return [Basic(), Dakota()] diff --git a/force_bdss/workspecs/multi_criteria_optimization.py b/force_bdss/workspecs/multi_criteria_optimization.py new file mode 100644 index 0000000000000000000000000000000000000000..9e5e3152e68a168d0de2d2e5f0ff8d2031c1b25d --- /dev/null +++ b/force_bdss/workspecs/multi_criteria_optimization.py @@ -0,0 +1,13 @@ +from traits.api import HasStrictTraits, String + + +class MultiCriteriaOptimization(HasStrictTraits): + name = String() + + @classmethod + def from_json(cls, json_data): + self = cls( + name=json_data["name"] + ) + + return self diff --git a/force_bdss/workspecs/workflow.py b/force_bdss/workspecs/workflow.py new file mode 100644 index 0000000000000000000000000000000000000000..934c7781d36daf2c80d3f680f1616573223b9c3e --- /dev/null +++ b/force_bdss/workspecs/workflow.py @@ -0,0 +1,20 @@ +from traits.api import HasStrictTraits, Instance, String, List + +from .multi_criteria_optimization import MultiCriteriaOptimization + + +class Workflow(HasStrictTraits): + name = String() + multi_criteria_optimization = Instance(MultiCriteriaOptimization) + key_performance_indicators = List(String) + + @classmethod + def from_json(cls, json_data): + self = cls( + multi_criteria_optimization=MultiCriteriaOptimization.from_json( + json_data["multi_criteria_optimization"] + ), + key_performance_indicators=json_data["key_performance_indicators"] + ) + + return self diff --git a/requirements/dev_requirements.txt b/requirements/dev_requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..39304807fbc7bd47cb8a72ff873c076b694e4fbe --- /dev/null +++ b/requirements/dev_requirements.txt @@ -0,0 +1 @@ +flake8 diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..2be76f218f7363cdffadd53391f60698221a5d61 --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,2 @@ +envisage==4.6.0 +click==6.7 diff --git a/setup.py b/setup.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c544f177233e07b060f03ec9bd59f0a2a8ac88dc 100644 --- a/setup.py +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +VERSION = "0.1.0.dev0" + +setup( + name="force_bdss", + version=VERSION, + entry_points={ + 'console_scripts': [ + 'force_bdss = force_bdss.cli.force_bdss:run', + ]}, + install_requires=[ + "envisage >= 4.6.0", + "click >= 6.7" + ] +)