Skip to content
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
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

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

## [1.3.3] - 2025-03-11

### Bug Fixes

- Make `midext_prob` last param in get/set methods
- Midline musn't use last of `set_param` args.\
Instead, it should use the one at `num_dims - 1`.

### Documentation

- Fix broken quickstart link.
- Add `emcee` to requirements for quickstart notebook.
- Explain midline extension probability in set/get.

### Testing

- `midext_prob` is last parameter in get/set methods.

## [1.3.2] - 2025-01-31

### Bug Fixes
Expand Down Expand Up @@ -848,6 +866,7 @@ Almost the entire API has changed. I'd therefore recommend to have a look at the
- fix pyproject.toml typo
- add pre-commit hook to check commit msg

[1.3.3]: https://github.com/rmnldwg/lymph/compare/1.3.2...1.3.3
[1.3.2]: https://github.com/rmnldwg/lymph/compare/1.3.1...1.3.2
[1.3.1]: https://github.com/rmnldwg/lymph/compare/1.3.0...1.3.1
[1.3.0]: https://github.com/rmnldwg/lymph/compare/1.2.3...1.3.0
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The mathematical details of the models can be found in in our earlier publicatio
Get started
===========

To learn how to use this package, head over to our `documentation <https://lymph-model.readthedocs.io>`_ where we explain the API of the package and also provide a `quickstart guide <https://lymph-model.readthedocs.io/en/latest/quickstart.html>`_.
To learn how to use this package, head over to our `documentation <https://lymph-model.readthedocs.io>`_ where we explain the API of the package and also provide a `quickstart guide <https://lymph-model.readthedocs.io/stable/quickstart.html>`_.

The implementation is pure-python and has only a few dependencies. However, it is intended to be used with powerful inference algorithms, e.g. the great sampling package `emcee <https://github.com/dfm/emcee>`_, which we used for our results.

Expand Down
16 changes: 10 additions & 6 deletions lymph/models/midline.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,13 @@ def get_params(

Includes the spread parameters from the call to :py:meth:`get_spread_params`
and the distribution parameters from the call to
:py:meth:`~.diagnosis_times.Composite.get_distribution_params`.
:py:meth:`~.diagnosis_times.Composite.get_distribution_params`. It also appends
the probability of midline extension to the end of the returned params.
"""
params = {}
params["midext_prob"] = self.midext_prob
params.update(self.get_spread_params(as_flat=as_flat))
params.update(self.get_distribution_params(as_flat=as_flat))
params["midext_prob"] = self.midext_prob

if as_flat or not as_dict:
params = utils.flatten(params)
Expand Down Expand Up @@ -482,11 +483,14 @@ def set_params(
"""Set all parameters of the model.

Combines the calls to :py:meth:`.set_spread_params` and
:py:meth:`.set_distribution_params`.
:py:meth:`.set_distribution_params`. Additionally, it sets the probability
for midline extension. Note that this parameter is always the last one that
is set after the spread and distribution parameters.
"""
first, args = utils.popfirst(args)
self.midext_prob = kwargs.get("midext_prob", first) or self.midext_prob
args = self.set_spread_params(*args, **kwargs)
last_param_idx = self.get_num_dims() - 1
before, last, after = utils.popat(args, idx=last_param_idx)
self.midext_prob = kwargs.get("midext_prob", last) or self.midext_prob
args = self.set_spread_params(*(before + after), **kwargs)
return self.set_distribution_params(*args, **kwargs)

def load_patient_data(
Expand Down
49 changes: 49 additions & 0 deletions lymph/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,55 @@ def popfirst(seq: Sequence[Any]) -> tuple[Any, Sequence[Any]]:
return None, seq


def poplast(seq: Sequence[Any]) -> tuple[Sequence[Any], Any]:
"""Return the sequence without the last element and the last element.

If the sequence is empty, the first element will be ``None`` and the second just
the empty sequence. Example:

>>> poplast([1, 2, 3])
([1, 2], 3)
>>> poplast([])
([], None)
"""
first, rest = popfirst(seq[::-1])
return rest[::-1], first


def popat(seq: Sequence[Any], idx: int) -> tuple[Sequence[Any], Any, Sequence[Any]]:
"""Return the sequence before, the element at, and the sequence after ``idx``.

If the sequence is empty, the sequence before and after will be empty and the
element at ``idx`` will be ``None``.

If ``idx`` is too large, the sequence before will be the whole sequence, the element
at ``idx`` will be ``None``, and the sequence after will be empty. Example:

>>> popat([1, 2, 3], 1)
([1], 2, [3])
>>> popat([], 0)
([], None, [])
>>> popat([1, 2, 3], -1)
([1, 2], 3, [])
>>> popat([1, 2, 3], -10)
([], None, [1, 2, 3])
>>> popat([1, 2, 3], 10)
([1, 2, 3], None, [])
>>> popat((1, 2, 3), 10)
((1, 2, 3), None, ())
"""
if idx < 0:
idx += len(seq)

if idx < 0:
return type(seq)(), None, seq

if idx >= len(seq):
return seq, None, type(seq)()

return seq[:idx], seq[idx], seq[idx + 1 :]


def flatten(mapping, parent_key="", sep="_") -> dict:
"""Flatten a nested dictionary.

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ docs = [
"ipython",
"matplotlib",
"scipy",
"emcee",
]

[project.urls]
Expand Down
15 changes: 15 additions & 0 deletions tests/binary_midline_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ def test_get_set_params_order(self) -> None:
self.assertEqual(unused_param, params_to_set[-1])
self.assertEqual(params_to_set[:-1].tolist(), returned_params)

def test_midext_prob_is_last(self) -> None:
"""Check that the midline probability is the last parameter."""
expected_midext_prob = self.rng.uniform()
self.model.midext_prob = expected_midext_prob
self.assertEqual(
list(self.model.get_params(as_dict=False))[-1],
expected_midext_prob,
)

expected_midext_prob = self.rng.uniform()
num_dims = self.model.get_num_dims()
params_to_set = [0.0] * (num_dims - 1) + [expected_midext_prob]
self.model.set_params(*params_to_set)
self.assertEqual(self.model.midext_prob, expected_midext_prob)


class MidlineLikelihoodTestCase(
fixtures.MidlineFixtureMixin,
Expand Down