Skip to content

Commit 971c367

Browse files
authored
Merge pull request #59 from instadeepai/58-instanovo-100-code-tests
InstaNovo 1.0.0 code tests
2 parents a8fc03e + 350eb33 commit 971c367

28 files changed

+2582
-2
lines changed

.github/workflows/pytest-multiversion.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
python-version: ["3.10", "3.11"] # "3.12"
18-
os: [ubuntu-latest, windows-latest] # macos-latest
18+
os: [ubuntu-latest] #windows-latest # macos-latest
1919
runs-on: ${{ matrix.os }}
2020

2121
steps:
@@ -36,6 +36,7 @@ jobs:
3636
pre-commit run --all-files -c .pre-commit-config.yaml
3737
- name: Test with pytest
3838
run: |
39+
python -m instanovo.scripts.get_zenodo_record
3940
pytest -v --alluredir=allure_results --cov-report=html --cov --cov-config=.coveragerc --random-order
4041
coverage report -m
4142
- name: Test notebooks on Linux

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,6 @@ docs/reference
171171
coverage/
172172
checkpoints/
173173
data/
174-
docs/
175174
docs_public/
176175
logs/
177176
mlruns/

docs/LICENSE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--8<-- "LICENSE.md"
64.4 KB
Loading

docs/assets/instadeep-logo.png

14.7 KB
Loading

docs/gen_ref_nav.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Generate the code reference pages and navigation."""
2+
3+
# This script is used by the mkdocs-gen-files plugin (https://oprypin.github.io/mkdocs-gen-files/)
4+
# for MkDocs (https://www.mkdocs.org/). It creates for each module in the code a stub page
5+
# and it creates a "docs/reference/SUMMARY.md" page which contains a Table of Contents with links to
6+
# all the stub pages. When MkDocs runs, it will populate the stub pages with the documentation
7+
# pulled from the docstrings
8+
from __future__ import annotations
9+
10+
from pathlib import Path
11+
12+
import mkdocs_gen_files
13+
14+
# Folders for which we don't want to create code documentation but which can contain *.py files
15+
IGNORE_DIRS = ("build", "docs_public", "docs", "tests", "scripts", "utils", ".venv")
16+
17+
18+
def is_ignored_directory(module_path: Path) -> bool:
19+
"""Check if the module path is within any ignored directory."""
20+
return any(part in IGNORE_DIRS for part in module_path.parts)
21+
22+
23+
def is_ignored_file(module_path: Path) -> bool:
24+
"""Check if the file is a test file or ignored file."""
25+
return module_path.parts[-1].endswith("_test") or module_path.parts[-1] in (
26+
"mlflow_auth",
27+
"types",
28+
"constants",
29+
)
30+
31+
32+
def process_python_files(source_directory: str, module_name: str) -> None:
33+
"""Generate documentation paths for Python files in the source directory."""
34+
nav = mkdocs_gen_files.Nav()
35+
36+
for python_file in sorted(Path(source_directory).rglob("*.py")):
37+
relative_module_path = python_file.relative_to(source_directory).with_suffix("")
38+
39+
if not is_ignored_directory(relative_module_path) and not is_ignored_file(
40+
relative_module_path
41+
):
42+
doc_path = python_file.relative_to(
43+
source_directory, module_name
44+
).with_suffix(".md")
45+
full_doc_path = Path("reference", doc_path)
46+
47+
parts = tuple(relative_module_path.parts)
48+
49+
if parts[-1] == "__init__":
50+
parts = parts[:-1]
51+
doc_path = doc_path.with_name("index.md")
52+
full_doc_path = full_doc_path.with_name("index.md")
53+
elif parts[-1] == "__main__":
54+
continue
55+
56+
nav[parts] = doc_path.as_posix()
57+
58+
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
59+
ident = ".".join(parts)
60+
fd.write(f"::: {ident}")
61+
62+
mkdocs_gen_files.set_edit_path(full_doc_path, ".." / python_file)
63+
64+
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
65+
nav_file.writelines(nav.build_literate_nav())
66+
67+
68+
process_python_files(source_directory=".", module_name="instanovo")

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
--8<-- "README.md"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
defaults:
2+
- instanovo_base
3+
- _self_
4+
5+
# Model parameters
6+
dim_model: 320
7+
n_head: 20
8+
dim_feedforward: 1280
9+
n_layers: 6
10+
dropout: 0.

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import random
5+
import sys
6+
from typing import Any
7+
8+
import numpy as np
9+
import pandas as pd
10+
import pytest
11+
import pytorch_lightning as ptl
12+
import torch
13+
from hydra import compose, initialize
14+
from omegaconf import DictConfig, open_dict
15+
16+
from instanovo.inference.knapsack_beam_search import KnapsackBeamSearchDecoder
17+
from instanovo.transformer.model import InstaNovo
18+
from instanovo.transformer.predict import _setup_knapsack
19+
20+
21+
# Add the root directory to the PYTHONPATH
22+
# This allows pytest to find the modules for testing
23+
24+
root_dir = os.path.dirname(os.path.dirname(__file__))
25+
sys.path.append(root_dir)
26+
27+
28+
def reset_seed(seed: int = 42) -> None:
29+
"""Function to reset seeds."""
30+
torch.manual_seed(seed)
31+
if torch.cuda.is_available():
32+
torch.cuda.manual_seed_all(seed)
33+
np.random.seed(seed)
34+
random.seed(seed)
35+
ptl.seed_everything(seed)
36+
37+
38+
@pytest.fixture()
39+
def _reset_seed() -> None:
40+
"""A pytest fixture to reset the seeds at the start of relevant tests."""
41+
reset_seed()
42+
43+
44+
@pytest.fixture(scope="session")
45+
def checkpoints_dir() -> str:
46+
"""A pytest fixture to create and provide the absolute path of a 'checkpoints' directory.
47+
48+
Ensures the directory exists for storing checkpoint files during the test session.
49+
"""
50+
checkpoints_dir = "checkpoints"
51+
os.makedirs(checkpoints_dir, exist_ok=True)
52+
return os.path.abspath(checkpoints_dir)
53+
54+
55+
@pytest.fixture(scope="session")
56+
def instanovo_config() -> DictConfig:
57+
"""A pytest fixture to read in a Hydra config for the Instanovo model unit and integration tests."""
58+
with initialize(version_base=None, config_path="../instanovo/configs"):
59+
cfg = compose(config_name="instanovo_unit_test")
60+
61+
sub_configs_list = ["model", "dataset", "residues"]
62+
for sub_name in sub_configs_list:
63+
if sub_name in cfg:
64+
with open_dict(cfg):
65+
temp = cfg[sub_name]
66+
del cfg[sub_name]
67+
cfg.update(temp)
68+
69+
return cfg
70+
71+
72+
@pytest.fixture(scope="session")
73+
def instanovo_inference_config() -> DictConfig:
74+
"""A pytest fixture to read in a Hydra config for inference of the Instanovo model unit and integration tests."""
75+
with initialize(version_base=None, config_path="../instanovo/configs/inference"):
76+
cfg = compose(config_name="unit_test")
77+
78+
return cfg
79+
80+
81+
@pytest.fixture(scope="session")
82+
def dir_paths() -> tuple[str, str]:
83+
"""A pytest fixture that returns the root and data directories for the unit and integration tests."""
84+
root_dir = "./tests/instanovo_test_resources"
85+
data_dir = os.path.join(root_dir, "example_data")
86+
return root_dir, data_dir
87+
88+
89+
@pytest.fixture(scope="session")
90+
def instanovo_checkpoint(dir_paths: tuple[str, str]) -> str:
91+
"""A pytest fixture that returns the InstaNovo model checkpoint used for unit and integration tests."""
92+
root_dir, _ = dir_paths
93+
return os.path.join(root_dir, "model.ckpt")
94+
95+
96+
@pytest.fixture(scope="session")
97+
def instanovo_model(
98+
instanovo_checkpoint: str,
99+
) -> tuple[Any, Any]:
100+
"""A pytest fixture that returns the InstaNovo model and config used for unit and integration tests."""
101+
model, config = InstaNovo.load(path=instanovo_checkpoint)
102+
return model, config
103+
104+
105+
@pytest.fixture(scope="session")
106+
def residue_set(instanovo_model: tuple[Any, Any]) -> Any:
107+
"""A pytest fixture to return the model's residue set used for unit and integration tests."""
108+
model, _ = instanovo_model
109+
return model.residue_set
110+
111+
112+
@pytest.fixture(scope="session")
113+
def instanovo_output(dir_paths: tuple[str, str]) -> pd.DataFrame:
114+
"""A pytest fixture to load the pre-computed InstaNovo model predictions for unit and integration tests."""
115+
root_dir, _ = dir_paths
116+
return pd.read_csv(os.path.join(root_dir, "predictions.csv"))
117+
118+
119+
@pytest.fixture(scope="session")
120+
def knapsack_dir(dir_paths: tuple[str, str]) -> str:
121+
"""A pytest fixture to create and provide the absolute path of a 'knapsack' directory within the checkpoints directory for storing test artifacts."""
122+
root_dir, _ = dir_paths
123+
knapsack_dir = os.path.join(root_dir, "example_knapsack")
124+
return os.path.abspath(knapsack_dir)
125+
126+
127+
@pytest.fixture(scope="session")
128+
def setup_knapsack_decoder(
129+
instanovo_model: tuple[Any, Any], knapsack_dir: str
130+
) -> KnapsackBeamSearchDecoder:
131+
"""A pytest fixture to create a Knapsack object."""
132+
model, _ = instanovo_model
133+
134+
if os.path.exists(knapsack_dir):
135+
decoder = KnapsackBeamSearchDecoder.from_file(model=model, path=knapsack_dir)
136+
print("Loaded knapsack decoder.")
137+
138+
else:
139+
knapsack = _setup_knapsack(model)
140+
141+
knapsack.save(path=knapsack_dir)
142+
print("Created and saved knapsack.")
143+
144+
decoder = KnapsackBeamSearchDecoder(model, knapsack)
145+
print("Loaded knapsack decoder.")
146+
147+
return decoder

0 commit comments

Comments
 (0)