Skip to content

Commit 9b9a80b

Browse files
authored
Merge pull request #63 from DynamicsAndNeuralSystems/jmoo2880-YAML-restructure
Add optional dependency checks + SPI filter function
2 parents 8f297ca + 4fdce16 commit 9b9a80b

File tree

14 files changed

+2501
-2331
lines changed

14 files changed

+2501
-2331
lines changed

.github/workflows/run_unit_tests.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,35 @@ on:
44
push:
55

66
jobs:
7-
build:
7+
test-ubuntu:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
1111
python-version: ["3.8", "3.9"]
1212
steps:
13-
- uses: actions/checkout@v3
13+
- uses: actions/checkout@v4
1414
- name: Setup python ${{ matrix.python-version }}
15-
uses: actions/setup-python@v4
15+
uses: actions/setup-python@v5
1616
with:
1717
python-version: ${{ matrix.python-version }}
1818
cache: 'pip'
1919
- name: Install octave
2020
run: |
2121
sudo apt-get update
2222
sudo apt-get install -y build-essential octave
23-
- name: Install dependencies
23+
- name: Install pyspi dependencies
2424
run: |
2525
python -m pip install --upgrade pip
2626
pip install -r requirements.txt
2727
pip install .
28-
- name: Run pyspi calculator unit tests
28+
- name: Run pyspi calculator/utils unit tests
2929
run: |
3030
pytest -v ./tests/test_calc.py
31+
pytest -v ./tests/test_utils.py
3132
- name: Run pyspi SPI unit tests
3233
run: |
3334
pytest -v ./tests/test_SPIs.py
35+
36+
37+
3438

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "pyspi"
7-
version = "0.4.2"
7+
version = "1.0.0"
88
authors = [
99
{ name ="Oliver M. Cliff", email="[email protected]"},
1010
]

pyspi/calculator.py

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# From this package
1010
from .data import Data
11-
from .utils import convert_mdf_to_ddf
11+
from .utils import convert_mdf_to_ddf, check_optional_deps
1212

1313

1414
class Calculator:
@@ -31,15 +31,17 @@ class Calculator:
3131
labels (array_like, optional):
3232
Any set of strings by which you want to label the calculator. This can be useful later for classification purposes, defaults to None.
3333
subset (str, optional):
34-
A pre-configured subset of SPIs to use. Options are "all", "fast", "sonnet", "octaveless", or "fabfour", defaults to "all".
34+
A pre-configured subset of SPIs to use. Options are "all", "fast", "sonnet", or "fabfour", defaults to "all".
3535
configfile (str, optional):
3636
The location of the YAML configuration file for a user-defined subset. See :ref:`Using a reduced SPI set`, defaults to :code:`'</path/to/pyspi>/pyspi/config.yaml'`
3737
"""
38+
_optional_dependencies = None
3839

3940
def __init__(
4041
self, dataset=None, name=None, labels=None, subset="all", configfile=None
4142
):
4243
self._spis = {}
44+
self._excluded_spis = list()
4345

4446
# Define configfile by subset if it was not specified
4547
if configfile is None:
@@ -55,17 +57,20 @@ def __init__(
5557
configfile = (
5658
os.path.dirname(os.path.abspath(__file__)) + "/fabfour_config.yaml"
5759
)
58-
elif subset == "octaveless":
59-
configfile = (
60-
os.path.dirname(os.path.abspath(__file__)) + "/octaveless_config.yaml"
61-
)
6260
# If no configfile was provided but the subset was not one of the above (or the default 'all'), raise an error
6361
elif subset != "all":
6462
raise ValueError(
65-
f"Subset '{subset}' does not exist. Try 'all' (default), 'fast', 'sonnet', 'octaveless', or 'fabfour'."
63+
f"Subset '{subset}' does not exist. Try 'all' (default), 'fast', 'sonnet', or 'fabfour'."
6664
)
6765
else:
6866
configfile = os.path.dirname(os.path.abspath(__file__)) + "/config.yaml"
67+
68+
# add dependency checks here if the calculator is being instantiated for the first time
69+
if not Calculator._optional_dependencies:
70+
# check if optional dependencies exist
71+
print("Checking if optional dependencies exist...")
72+
Calculator._optional_dependencies = check_optional_deps()
73+
6974
self._load_yaml(configfile)
7075

7176
duplicates = [
@@ -79,7 +84,38 @@ def __init__(
7984
self._name = name
8085
self._labels = labels
8186

82-
print("Number of SPIs: {}".format(len(self.spis)))
87+
print(f"="*100)
88+
print(f"Number of SPIs: {len(self.spis)}\n")
89+
if len(self._excluded_spis) > 0:
90+
missing_deps = [dep for dep, is_met in self._optional_dependencies.items() if not is_met]
91+
print("**** SPI Initialisation Warning ****")
92+
print("\nSome dependencies were not detected, which has led to the exclusion of certain SPIs:")
93+
print("\nMissing Dependencies:")
94+
95+
for dep in missing_deps:
96+
print(f"- {dep}")
97+
98+
print(f"\nAs a result, a total of {len(self._excluded_spis)} SPI(s) have been excluded:\n")
99+
100+
dependency_groups = {}
101+
for spi in self._excluded_spis:
102+
for dep in spi[1]:
103+
if dep not in dependency_groups:
104+
dependency_groups[dep] = []
105+
dependency_groups[dep].append(spi[0])
106+
107+
for dep, spis in dependency_groups.items():
108+
print(f"\nDependency - {dep} - affects {len(spis)} SPI(s)")
109+
print("Excluded SPIs:")
110+
for spi in spis:
111+
print(f" - {spi}")
112+
113+
print(f"\n" + "="*100)
114+
print("\nOPTIONS TO PROCEED:\n")
115+
print(f" 1) Install the following dependencies to access all SPIs: [{', '.join(missing_deps)}]")
116+
callable_name = "{Calculator/CalculatorFrame}"
117+
print(f" 2) Continue with a reduced set of {self.n_spis} SPIs by calling {callable_name}.compute(). \n")
118+
print(f"="*100 + "\n")
83119

84120
if dataset is not None:
85121
self.load_dataset(dataset)
@@ -180,10 +216,20 @@ def _load_yaml(self, document):
180216
print("*** Importing module {}".format(module_name))
181217
module = importlib.import_module(module_name, __package__)
182218
for fcn in yf[module_name]:
219+
deps = yf[module_name][fcn].get('dependencies')
220+
if deps is not None:
221+
all_deps_met = all(Calculator._optional_dependencies.get(dep, False) for dep in deps)
222+
if not all_deps_met:
223+
current_base_spi = yf[module_name][fcn]
224+
print(f"Optional dependencies: {deps} not met. Skipping {len(current_base_spi.get('configs'))} SPI(s):")
225+
for params in current_base_spi.get('configs'):
226+
print(f"*SKIPPING SPI: {module_name}.{fcn}(x,y,{params})...")
227+
self._excluded_spis.append([f"{fcn}(x,y,{params})", deps])
228+
continue
183229
try:
184-
for params in yf[module_name][fcn]:
230+
for params in yf[module_name][fcn].get('configs'):
185231
print(
186-
f"[{self.n_spis}] Adding SPI {module_name}.{fcn}(x,y,{params})..."
232+
f"[{self.n_spis}] Adding SPI {module_name}.{fcn}(x,y,{params})"
187233
)
188234
spi = getattr(module, fcn)(**params)
189235
self._spis[spi.identifier] = spi

0 commit comments

Comments
 (0)