From 7d016ed3bf7632a0b84cdec21c4b96b224b4e16a Mon Sep 17 00:00:00 2001 From: Viet Dung Nguyen <60036798+rxng8@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:50:54 +0200 Subject: [PATCH 1/4] Add unit testing and example (#29) * push initial unit testing workflow * config example unit testing --- .github/workflows/python-package-conda.yml | 34 ++++++++++++++++++++++ pytest.ini | 6 ++++ tests/compartment_test.py | 22 ++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 .github/workflows/python-package-conda.yml create mode 100644 pytest.ini create mode 100644 tests/compartment_test.py diff --git a/.github/workflows/python-package-conda.yml b/.github/workflows/python-package-conda.yml new file mode 100644 index 0000000..404cbae --- /dev/null +++ b/.github/workflows/python-package-conda.yml @@ -0,0 +1,34 @@ +name: Python Package using Conda + +on: [push] + +jobs: + build-linux: + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: '3.10' + - name: Add conda to system path + run: | + # $CONDA is an environment variable pointing to the root of the miniconda directory + echo $CONDA/bin >> $GITHUB_PATH + - name: Install current library and dependencies + run: | + pip install -e . + # - name: Lint with flake8 + # run: | + # conda install flake8 + # # stop the build if there are Python syntax errors or undefined names + # flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + # flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + conda install pytest + pytest diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5f7e5da --- /dev/null +++ b/pytest.ini @@ -0,0 +1,6 @@ +# content of pytest.ini +[pytest] +python_files = *_test.py +python_classes = *Test +python_functions = test_* *_test +testpaths = tests \ No newline at end of file diff --git a/tests/compartment_test.py b/tests/compartment_test.py new file mode 100644 index 0000000..85bc3b9 --- /dev/null +++ b/tests/compartment_test.py @@ -0,0 +1,22 @@ + +import pathlib +import sys + +# NOTE: VN: Since we have installed using `pip install -e .` in the workflow file, we +# might not need to manually add to the path +sys.path.append(str(pathlib.Path(__file__).parent.parent)) + +# import ngcsimlib + +class CompartmentTest: + + def test_import(self): + success = False + try: + from ngcsimlib.compartment import Compartment + success = True + except: + success = False + assert success, "Import failed!" + + From b0d91b17830c5725ea74b0da2c3c71a7375e8b42 Mon Sep 17 00:00:00 2001 From: Will Gebhardt Date: Tue, 9 Jul 2024 16:04:47 -0400 Subject: [PATCH 2/4] Added functionality to the resolver --- ngcsimlib/compilers/component_compiler.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ngcsimlib/compilers/component_compiler.py b/ngcsimlib/compilers/component_compiler.py index 28ea42d..eed9b92 100644 --- a/ngcsimlib/compilers/component_compiler.py +++ b/ngcsimlib/compilers/component_compiler.py @@ -37,9 +37,16 @@ def parse(component, compile_key): the compartments needed """ - (pure_fn, output_compartments), ( - args, parameters, compartments, parse_varnames) = \ - get_resolver(component.__class__, compile_key) + if component.__class__.__dict__.get("auto_resolve", True): + (pure_fn, output_compartments), ( + args, parameters, compartments, parse_varnames) = \ + get_resolver(component.__class__, compile_key) + else: + build_method = component.__class__.__dict__.get(f"build_{compile_key}", None) + if build_method is None: + critical(f"Component {component.name} if flagged to not use resolvers but " + f"does not have a build_{compile_key} method") + return build_method(component) if parse_varnames: args = [] From 087706e2b05f28dae18a5570face2e1743dc23bf Mon Sep 17 00:00:00 2001 From: Will Gebhardt Date: Mon, 15 Jul 2024 14:16:37 -0400 Subject: [PATCH 3/4] Sped up compiler --- ngcsimlib/compilers/command_compiler.py | 4 ++-- ngcsimlib/compilers/component_compiler.py | 6 ++++-- ngcsimlib/compilers/op_compiler.py | 9 +++++++-- ngcsimlib/metaComponent.py | 9 ++++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/ngcsimlib/compilers/command_compiler.py b/ngcsimlib/compilers/command_compiler.py index 8c79051..e0c51a5 100644 --- a/ngcsimlib/compilers/command_compiler.py +++ b/ngcsimlib/compilers/command_compiler.py @@ -92,8 +92,8 @@ def compiled(compartment_values, **kwargs): critical(f"Missing keyword argument \"{n}\" in compiled function." f"\tExpected keyword arguments {needed_args}") - for exc, outs, name in exc_order: - _comps = {key: compartment_values[key] for key in needed_comps} + for exc, outs, name, comp_ids in exc_order: + _comps = {key: compartment_values[key] for key in comp_ids} vals = exc(**kwargs, **_comps) if len(outs) == 1: compartment_values[outs[0]] = vals diff --git a/ngcsimlib/compilers/component_compiler.py b/ngcsimlib/compilers/component_compiler.py index eed9b92..20691a6 100644 --- a/ngcsimlib/compilers/component_compiler.py +++ b/ngcsimlib/compilers/component_compiler.py @@ -102,11 +102,13 @@ def compile(component, resolver): funParams = {narg: component.__dict__[narg] for narg in params} + comp_key_key = [(narg.split('/')[-1], narg) for narg in comp_ids] + def compiled(**kwargs): funArgs = {narg: kwargs.get(narg) for narg in _args} - funComps = {narg.split('/')[-1]: kwargs.get(narg) for narg in comp_ids} + funComps = {knarg: kwargs.get(narg) for knarg, narg in comp_key_key} return pure_fn.__func__(**funParams, **funArgs, **funComps) - exc_order.append((compiled, out_ids, component.name)) + exc_order.append((compiled, out_ids, component.name, comp_ids)) return exc_order diff --git a/ngcsimlib/compilers/op_compiler.py b/ngcsimlib/compilers/op_compiler.py index 36efce9..2f32fff 100644 --- a/ngcsimlib/compilers/op_compiler.py +++ b/ngcsimlib/compilers/op_compiler.py @@ -85,8 +85,13 @@ def compile(op): else: iids.append(str(s.path)) + additional_idds = [] + for _, _, _, _iids in exc_order: + additional_idds.extend(_iids) + + # print(additional_idds) def _op_compiled(**kwargs): - computed_values = [cmd(**kwargs) for cmd, _, _ in exc_order] + computed_values = [cmd(**kwargs) for cmd, _, _, _ in exc_order] compartment_args = [kwargs.get(narg) for narg in iids] _val_loc = 0 @@ -103,4 +108,4 @@ def _op_compiled(**kwargs): return op.operation(*_args) - return (_op_compiled, [str(output)], "op") + return (_op_compiled, [str(output)], op.__class__.__name__, iids + additional_idds) diff --git a/ngcsimlib/metaComponent.py b/ngcsimlib/metaComponent.py index 7d2b7cb..8d5b6f4 100644 --- a/ngcsimlib/metaComponent.py +++ b/ngcsimlib/metaComponent.py @@ -1,7 +1,7 @@ from ngcsimlib.compartment import Compartment from ngcsimlib.utils import get_current_context from ngcsimlib.utils.help import Guides -from ngcsimlib.logger import debug +from ngcsimlib.logger import debug, warn class MetaComponent(type): @@ -82,7 +82,14 @@ def wrapped_init(self, *args, **kwargs): else: cls.pre_init(self, *args, **kwargs) x._orig_init(self, *args, **kwargs) + args_count = self._orig_init.__code__.co_argcount + _kwargs = self._orig_init.__code__.co_varnames[:args_count] + for key, value in kwargs.items(): + if key not in _kwargs: + debug(f"There is an extra param {key} in component constructor for {self.name}") cls.post_init(self, *args, **kwargs) + if hasattr(self, "_setup"): + self._setup() x.__init__ = wrapped_init From f9289c5413404349c2daf4efcc5ddbbe906a3d75 Mon Sep 17 00:00:00 2001 From: Will Gebhardt Date: Tue, 23 Jul 2024 14:23:34 -0400 Subject: [PATCH 4/4] added compartment metadata --- ngcsimlib/compartment.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ngcsimlib/compartment.py b/ngcsimlib/compartment.py index 9e331dc..474360f 100644 --- a/ngcsimlib/compartment.py +++ b/ngcsimlib/compartment.py @@ -31,7 +31,7 @@ def is_compartment(cls, obj): """ return hasattr(obj, "_is_compartment") - def __init__(self, initial_value=None, static=False, is_input=False): + def __init__(self, initial_value=None, static=False, is_input=False, display_name=None, units=None): """ Builds a compartment to be used inside a component. It is important to note that building compartments @@ -56,6 +56,8 @@ def __init__(self, initial_value=None, static=False, is_input=False): self.path = None self.is_input = is_input self._is_destination = False + self._display_name = display_name + self._units = units def _setup(self, current_component, key): """ @@ -131,3 +133,12 @@ def is_wired(self): return True return self._is_destination + + @property + def display_name(self): + return self._display_name if self._display_name is not None else self.name + + @property + def units(self): + return self._units if self._units is not None else "dimensionless" +