Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.2.3 #91

Merged
merged 6 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
python3 -m pip install .[test]
- name: Run tests
run: |
coverage run --omit=tests/*,*/_*.py -m unittest discover -v -p *_test.py .
coverage run --omit=tests/*,*/_*.py -m pytest
coverage xml
- name: Upload to codecov
uses: codecov/codecov-action@v3
27 changes: 25 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@

All notable changes to this project will be documented in this file.

<a name="1.2.3"></a>

## [1.2.3] - 2024-07-26

### Features

- (**mid**) Add missing `binary` constructor to `Midline` model. Now all models have a `binary` and `trinary` constructor.

### Styling

- Add rules to [ruff].

### Testing

- Make suite testable with [pytest].

### Ci

- Switch to [pytest] for testing.

<a name="1.2.2"></a>

## [1.2.2] - 2024-06-25
Expand All @@ -23,11 +43,11 @@ All notable changes to this project will be documented in this file.

### Styling

- Use ruff to fix lint and format code.
- Use [ruff] to fix lint and format code.

### Build

- Remove upper cap in deps.
- Remove upper cap in dependencies because of [this](https://iscinumpy.dev/post/bound-version-constraints/).

### Change

Expand Down Expand Up @@ -793,3 +813,6 @@ Almost the entire API has changed. I'd therefore recommend to have a look at the
[#41]: https://github.com/rmnldwg/lymph/issues/41
[#40]: https://github.com/rmnldwg/lymph/issues/40
[#38]: https://github.com/rmnldwg/lymph/issues/38

[ruff]: https://astral.sh/ruff
[pytest]: https://docs.pytest.org/en/stable/
56 changes: 38 additions & 18 deletions lymph/models/midline.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def __init__(
if is_symmetric["tumor_spread"]:
raise ValueError(
"If you want the tumor spread to be symmetric, consider using the "
"Bilateral class."
"Bilateral class.",
)
self.is_symmetric = is_symmetric

Expand All @@ -124,7 +124,7 @@ def __init__(
if self.use_midext_evo and use_central:
raise ValueError(
"Evolution to central tumor not yet implemented. Choose to use either "
"the central model or the midline extension evolution."
"the central model or the midline extension evolution.",
# Actually, this shouldn't be too hard, but we still need to think
# about it for a bit.
)
Expand Down Expand Up @@ -168,6 +168,13 @@ def __init__(
is_modality_leaf=False,
)

@classmethod
def binary(cls, *args, **kwargs) -> Midline:
"""Create a binary model."""
uni_kwargs = kwargs.pop("uni_kwargs", {})
uni_kwargs["allowed_states"] = [0, 1]
return cls(*args, uni_kwargs=uni_kwargs, **kwargs)

@classmethod
def trinary(cls, *args, **kwargs) -> Midline:
"""Create a trinary model."""
Expand Down Expand Up @@ -242,7 +249,7 @@ def unknown(self) -> models.Bilateral:
if self.marginalize_unknown:
return self._unknown
raise AttributeError(
"This instance does not marginalize over unknown midline extension."
"This instance does not marginalize over unknown midline extension.",
)

def get_tumor_spread_params(
Expand All @@ -262,15 +269,15 @@ def get_tumor_spread_params(

if self.use_mixing:
params["contra"] = self.noext.contra.get_tumor_spread_params(
as_flat=as_flat
as_flat=as_flat,
)
params["mixing"] = self.mixing_param
else:
params["noext"] = {
"contra": self.noext.contra.get_tumor_spread_params(as_flat=as_flat)
"contra": self.noext.contra.get_tumor_spread_params(as_flat=as_flat),
}
params["ext"] = {
"contra": self.ext.contra.get_tumor_spread_params(as_flat=as_flat)
"contra": self.ext.contra.get_tumor_spread_params(as_flat=as_flat),
}

if as_flat or not as_dict:
Expand All @@ -295,15 +302,15 @@ def get_lnl_spread_params(
if ext_lnl_params != noext_lnl_params:
raise ValueError(
"LNL spread params not synched between ext and noext models. "
"Returning the ext params."
"Returning the ext params.",
)

if self.use_central:
central_lnl_params = self.central.get_lnl_spread_params(as_flat=False)
if central_lnl_params != ext_lnl_params:
warnings.warn(
"LNL spread params not synched between central and ext models. "
"Returning the ext params."
"Returning the ext params.",
)

if as_flat or not as_dict:
Expand Down Expand Up @@ -412,7 +419,8 @@ def set_tumor_spread_params(
noext_contra_kwargs = global_kwargs.copy()
noext_contra_kwargs.update(kwargs.get("noext", {}).get("contra", {}))
args = self.noext.contra.set_tumor_spread_params(
*args, **noext_contra_kwargs
*args,
**noext_contra_kwargs,
)

ext_contra_kwargs = global_kwargs.copy()
Expand Down Expand Up @@ -521,7 +529,7 @@ def load_patient_data(
elif is_unknown.sum() > 0:
warnings.warn(
f"Discarding {is_unknown.sum()} patients where midline extension "
"is unknown."
"is unknown.",
)

def midext_evo(self) -> np.ndarray:
Expand Down Expand Up @@ -638,7 +646,9 @@ def obs_dist(
"""
if given_state_dist is None:
given_state_dist = self.state_dist(
t_stage=t_stage, mode=mode, central=central
t_stage=t_stage,
mode=mode,
central=central,
)

if given_state_dist.ndim == 2:
Expand All @@ -653,7 +663,9 @@ def obs_dist(
return np.stack(obs_dist)

def _hmm_likelihood(
self, log: bool = True, for_t_stage: str | None = None
self,
log: bool = True,
for_t_stage: str | None = None,
) -> float:
"""Compute the likelihood of the stored data under the hidden Markov model."""
llh = 0.0 if log else 1.0
Expand Down Expand Up @@ -771,7 +783,9 @@ def posterior_state_dist(
if given_state_dist is None:
utils.safe_set_params(self, given_params)
given_state_dist = self.state_dist(
t_stage=t_stage, mode=mode, central=central
t_stage=t_stage,
mode=mode,
central=central,
)

if given_state_dist.ndim == 2:
Expand Down Expand Up @@ -817,7 +831,9 @@ def marginalize(

if given_state_dist is None:
given_state_dist = self.state_dist(
t_stage=t_stage, mode=mode, central=central
t_stage=t_stage,
mode=mode,
central=central,
)

if given_state_dist.ndim == 2:
Expand Down Expand Up @@ -907,7 +923,7 @@ def draw_patients(

if self.use_central:
raise NotImplementedError(
"Drawing patients from the central model not yet supported."
"Drawing patients from the central model not yet supported.",
)

drawn_t_stages = rng.choice(
Expand All @@ -920,13 +936,16 @@ def draw_patients(
[
distributions[t_stage].draw_diag_times(rng=rng)
for t_stage in drawn_t_stages
]
],
)

if self.use_midext_evo:
midext_evo = self.midext_evo()
drawn_midexts = np.array(
[rng.choice(a=[False, True], p=midext_evo[t]) for t in drawn_diag_times]
[
rng.choice(a=[False, True], p=midext_evo[t])
for t in drawn_diag_times
],
)
else:
drawn_midexts = rng.choice(
Expand Down Expand Up @@ -956,7 +975,8 @@ def draw_patients(
seed=seed,
)
drawn_case_diags = np.concatenate(
[drawn_ipsi_diags, drawn_contra_diags], axis=1
[drawn_ipsi_diags, drawn_contra_diags],
axis=1,
)
drawn_diags[drawn_midexts == (case == "ext")] = drawn_case_diags

Expand Down
78 changes: 41 additions & 37 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
[build-system]
requires = [
"setuptools >= 61.0.0",
"setuptools_scm >= 7.0.0",
"wheel",
]
requires = ["setuptools >= 61.0.0", "setuptools_scm >= 7.0.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "lymph-model"
description = "Package for statistical modelling of lymphatic metastatic spread."
authors = [
{name = "Roman Ludwig", email = "roman.ludwig@usz.ch"}
]
authors = [{ name = "Roman Ludwig", email = "roman.ludwig@usz.ch" }]
readme = "README.rst"
requires-python = ">=3.10"
keywords = ["cancer", "metastasis", "lymphatic progression", "model"]
license = {text = "MIT"}
license = { text = "MIT" }
classifiers = [
"Development Status :: 3 - Alpha",
"License :: OSI Approved :: MIT License",
Expand All @@ -26,24 +20,12 @@ classifiers = [
"Operating System :: OS Independent",
"Topic :: Scientific/Engineering",
]
dependencies = [
"numpy",
"pandas",
"cachetools",
]
dependencies = ["numpy", "pandas", "cachetools"]
dynamic = ["version"]

[project.optional-dependencies]
test = [
"scipy",
"coverage",
"emcee",
]
dev = [
"pre-commit",
"pylint",
"git-cliff",
]
test = ["pytest", "scipy", "coverage", "emcee"]
dev = ["pre-commit"]
docs = [
"sphinx",
"sphinx-book-theme",
Expand All @@ -68,7 +50,12 @@ packages = ["lymph"]
include-package-data = true

[tool.setuptools.dynamic]
version = {attr = "lymph._version.version"}
version = { attr = "lymph._version.version" }


[tool.pytest.ini_options]
testpaths = ["tests"]
filterwarnings = ["ignore::pandas.errors.PerformanceWarning"]


[tool.isort]
Expand All @@ -87,7 +74,24 @@ all = true
exclude = ["tests", "docs"]

[tool.ruff.lint]
select = ["E", "F", "W", "B", "C", "R", "U", "D", "I", "S", "T", "A", "N", "NPY201"]
select = [
"E",
"F",
"W",
"B",
"C",
"R",
"U",
"D",
"I",
"S",
"T",
"A",
"N",
"COM",
"FURB",
"NPY201",
]
ignore = ["B028"]


Expand Down Expand Up @@ -141,20 +145,20 @@ filter_unconventional = true
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^docs", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactor" },
{ message = "^style", group = "Styling" },
{ message = "^test", group = "Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore", group = "Miscellaneous Tasks" },
{ body = ".*security", group = "Security" },
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^docs", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactor" },
{ message = "^style", group = "Styling" },
{ message = "^test", group = "Testing" },
{ message = "^chore\\(release\\): prepare for", skip = true },
{ message = "^chore", group = "Miscellaneous Tasks" },
{ body = ".*security", group = "Security" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
Expand Down
3 changes: 2 additions & 1 deletion tests/bayesian_unilateral_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Test the Bayesian Unilateral Model."""
import unittest

import numpy as np

Expand All @@ -7,7 +8,7 @@

class BayesianUnilateralModelTestCase(
fixtures.BinaryUnilateralModelMixin,
fixtures.IgnoreWarningsTestCase,
unittest.TestCase,
):
"""Test the Bayesian Unilateral Model."""

Expand Down
Loading
Loading