Skip to content

Share the modification registry with multiprocessing workers - #428

Open
GeorgWa wants to merge 4 commits into
mainfrom
fix/modification-registry-multiprocessing
Open

Share the modification registry with multiprocessing workers#428
GeorgWa wants to merge 4 commits into
mainfrom
fix/modification-registry-multiprocessing

Conversation

@GeorgWa

@GeorgWa GeorgWa commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Problem

A worker starts with spawn and imports alphabase again. It has only the modifications from modification.tsv. Run-time changes are absent: custom modifications, modloss filtering, lower-case AAs, a custom TSV.

calc_precursor_isotope_intensity used a fallback to one process. The other multiprocessing functions used the incorrect registry and gave no warning.

Change

_spawn_pool() copies MOD_DF in the parent and installs it in each worker, with get_modification_state() and set_modification_state(). The fallback and has_custom_mods() are removed.

The copy is complete, so a new column reaches the workers without a change here. #303 sent selected fields and thus needed a change for each new field.

parallel_imap() and parallel_apply() in alphabase/utils.py are now the only functions that make a pool. Converted: calc_precursor_isotope_info_mp, calc_precursor_isotope_intensity_mp, _apply_translate_modifications_mp, SpecLibDecoy.translate_to_decoy. This removes three copies of _batchify_df and 125 lines.

Also corrects calc_precursor_isotope_info, which sent process_bar= to a parameter with the name progress_bar and thus raised TypeError.

Tests

test_worker_registry_matches_parent compares the registry of the worker with the registry of the parent, after each of four run-time changes. It fails without this PR.

GeorgWa and others added 4 commits August 31, 2026 00:14
Workers are started with the "spawn" start method and re-import alphabase, so
they only ever saw the modifications in modification.tsv. Every runtime change
to the registry -- custom modifications, modloss filtering, lower-case AAs, a
custom TSV -- was silently absent in workers, raising KeyError on mod lookups.

MOD_DF is the single source of truth and update_all_by_MOD_DF() already rebuilds
everything derived from it, so a copy of MOD_DF is a complete snapshot of the
registry. get_modification_state()/set_modification_state() move that snapshot
between processes, and spawn_pool() installs it in each worker as it starts.
Because the snapshot is the whole table rather than a record of which mutators
ran, it stays correct as new mutators are added.

This replaces the fallback in SpecLibBase.calc_precursor_isotope_intensity,
which silently dropped to a single process whenever custom modifications were
present -- and which never covered the other three mutators anyway, since
has_custom_mods() only tests for the User-added classification. has_custom_mods
itself stays exported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All four pools had the same shape -- batchify, imap, optional progress bar,
concat -- reimplemented each time, with three separate batching helpers between
them. parallel_imap() and parallel_apply() carry that shape once, so callers no
longer construct a pool, choose a context, or think about what state a worker
needs; spawn_pool is now private.

Public signatures are unchanged. The helpers this removes were all private:
_batchify_df (defined twice, with different signatures), _batchify_series and
_count_batchify_df.

Fixes two progress bar bugs found on the way:

SpecLibBase.calc_precursor_isotope_info() passed process_bar= to a parameter
named progress_bar=, so the multiprocessing branch raised TypeError and was
dead. It also dropped mp_batch_size, which is now forwarded.

calc_precursor_isotope_intensity_mp() documented progress_bar as a flag but
peptdeep passes a callback. A callable was truthy, so it fell into the tqdm
branch and the callback was silently discarded. Progress now accepts True for a
tqdm bar, a callable to supply your own, or anything falsy for none, which also
restores the callback contract calc_precursor_isotope_info_mp already had.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "not full" install has no numba. Three tests call isotope functions that
need it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@jalew188 jalew188 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

return len(MOD_DF[MOD_DF["classification"] == _MOD_CLASSIFICATION_USER_ADDED]) > 0


def get_modification_state() -> pd.DataFrame:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe make explcit that these methods target multiprocessing ..

Comment thread alphabase/utils.py
return ctx.Pool(
processes,
initializer=set_modification_state,
initargs=(get_modification_state(),),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do I get correctly that get_modification_state() reads the MOD_DF from the main threads, and set_modification_state() gets it as input arg on the spawned threads?

"""
global MOD_DF
MOD_DF = mod_df
update_all_by_MOD_DF()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this mutates MOD_INFO_DICT, MOD_CHEM, etc .. does this work as intended on the worker threads?

Comment thread alphabase/utils.py


def _batch_count(obj, batch_size: int, group_by=None) -> int:
sizes = obj.groupby(group_by).size().values if group_by else [len(obj)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we duplicate logic of _batchify .. could this be returned by _batchify>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants