Skip to content

Phase weight rework 2 - #34

Closed
lauren-walters wants to merge 6 commits into
CederGroupHub:mainfrom
lauren-walters:phase-weight-rework-2
Closed

Phase weight rework 2#34
lauren-walters wants to merge 6 commits into
CederGroupHub:mainfrom
lauren-walters:phase-weight-rework-2

Conversation

@lauren-walters

Copy link
Copy Markdown
Collaborator

Summary

Major changes:

  • feature 1: ...
  • fix 1: ...

Todos

If this is work in progress, what else needs to be done?

  • feature 2: ...
  • fix 2:

Checklist

  • All existing tests pass.
  • Tests have been added for any new features/fixes.
  • Docstrings have been added in the Google docstring format.

Tip: Install pre-commit hooks to auto-check types and linting before every commit:

pip install -U pre-commit
pre-commit install

lauren-walters and others added 6 commits July 29, 2026 21:57
Ray's num_cpus=1 default per refinement task let as many concurrent tasks
run as detected CPUs, decoupled from the n_threads a task's BGMN subprocess
actually used -- at the old default (n_threads=8, 12 concurrent tasks) this
oversubscribed the machine ~8-10x in OS threads, which can starve Ray's own
GCS/raylet under sustained load.

- Add dara.hardware.detect_available_cores(): portable core-count detection
  (SLURM allocation, then OS affinity/cgroup quota, then os.cpu_count()).
- Add DaraSettings.RAY_NUM_CPUS (default: detected cores) and
  DaraSettings.BGMN_N_THREADS (default: 1), both overridable via
  DARA_RAY_NUM_CPUS / DARA_BGMN_N_THREADS env vars or ~/.dara.yaml.
- search_phases()'s ray.init() now passes num_cpus=RAY_NUM_CPUS explicitly
  instead of relying on Ray's own auto-detection (which ignores SLURM
  allocations and cgroup/container limits).
- Each refinement Ray task now requests num_cpus=n_threads via
  .options(...) at submission, instead of a hardcoded
  @ray.remote(num_cpus=1), so Ray throttles concurrency to
  floor(cores / n_threads) automatically -- with defaults this is 12
  workers x 1 thread on a 12-core machine; overriding n_threads upward
  safely drops concurrency to match.

Threads/concurrency/config only -- no change to search logic, pruning, or
node counts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The intensity-order pruning heuristic assumed phases are discovered in
decreasing order of abundance, terminating any branch where a newly-added
phase had more calculated peak intensity than one added earlier -- even
when that branch's overall fit was clearly better, discarding the correct
answer.

- Add should_prune_low_weight_fraction(intensity_out_of_order, parent_rwp,
  child_rwp, material_improvement_threshold=LOW_WEIGHT_FRACTION_RWP_IMPROVEMENT),
  a small standalone helper in dara.search.tree. The intensity-order signal
  is still computed exactly as before; the helper only decides whether it's
  fatal. LOW_WEIGHT_FRACTION_RWP_IMPROVEMENT defaults to 0.10: an
  out-of-order branch is now pruned only if it does NOT improve Rwp by at
  least 10% relative to its parent. With no parent to compare against (or a
  degenerate parent_rwp <= 0), the ordering signal alone still decides, same
  as before.
- Wire the helper into BaseSearchTree.expand_node() in place of the old
  inline boolean check.
- Add tests/test_search_tree.py: 9 direct unit tests of the helper plus 3
  integration-level tests driving the real expand_node() (score_phases/
  refine_phases mocked to avoid needing BGMN; calculate_fom_and_strain
  patched since it only reads a phase's own CIF file) confirming a
  materially-better branch is retained, a marginally-better one is still
  pruned, and in-order additions are never flagged regardless of fit.

Decision-only change: no recovery/branch-respawning mechanism, no change to
how many nodes/branches get explored beyond retaining branches this rule
would previously have discarded. On a deterministic 30-CIF subset of
dara-clustering-problem-2 (first 30 alphabetically), before/after node
count, statuses, and best Rwp are identical (15 nodes, best Rwp 29.73) --
the out-of-order case that arose there didn't clear the 10% bar either way,
consistent with the unit/integration tests being the primary proof this
works as designed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ruff format: collapse two multi-line assignments in tree.py's
  expand_node() and the whole of the new tests/test_search_tree.py to
  match the project's configured formatter (line-length=120). No other
  files in this branch's diff needed reformatting (hardware.py and
  settings.py were already compliant); pre-existing formatting drift in
  core.py/tree.py outside this branch's added lines was left untouched.
- ruff check across the whole repo: zero new findings in this branch's
  files. The 13 existing findings are all pre-existing, in files this
  branch never touched (notebooks, scripts/filter_cod.py,
  scripts/filter_icsd.py, tests/test_cif2str.py) -- left as-is per scope.
- Reviewed this branch's comments/docstrings against origin/main...HEAD
  for LLM-flavored padding, step narration, and restated-the-obvious
  comments. Found none to remove: every comment already documents
  non-obvious rationale (the hardware-detection priority order, the
  LOW_WEIGHT_FRACTION_RWP_IMPROVEMENT threshold's meaning, the
  .options(num_cpus=...) oversubscription-safety note) or is a concise,
  accurate docstring on a public helper. No comment/docstring text
  changed.

No logic or behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LOW_WEIGHT_FRACTION_RWP_IMPROVEMENT: 0.10 -> 0.08. Updated the constant's
doc comment to match.

tests/test_search_tree.py: the exactly-at-threshold and just-under-threshold
tests already derived their boundary values from
LOW_WEIGHT_FRACTION_RWP_IMPROVEMENT itself, so they needed no value changes
to track the new threshold -- only the exactly-at-threshold case needed a
tiny epsilon nudge, since 40.0 * (1 - 0.08) does not round-trip to exactly
0.08 in floating point and was landing on the wrong side of the `<` vs `<=`
boundary (0.07999999999999989 < 0.08), an artifact of the boundary
construction, not a bug in should_prune_low_weight_fraction. The marginal
(5%, still below 8%) and material (50%) cases needed no changes; only a
stale "10%" comment was updated to "8%".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merges upstream/main (15 commits ahead of where this branch forked,
including PR CederGroupHub#30's non-reverted RefinementMetrics addition to
RefinementResult) into this branch -- no conflicts.

RefinementResult.refinement_metrics is now a required field
(RefinementMetrics, whose only required field is rwp). The
_make_refinement_result test helper in tests/test_search_tree.py built a
RefinementResult without it, so pydantic validation failed:
"refinement_metrics Field required [type=missing]" -- this is what broke
CI (GitHub tests PR branches merged into the current base branch tip, not
the branch in isolation, which is why this only showed up there and not
in any isolated-branch reproduction).

Fix: pass a real RefinementMetrics(rwp=rwp) instance, matching how
production code constructs one (see get_result() / refine.py). No model
changes, no extra="ignore", nothing stubbed out.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Andrea-gm Andrea-gm closed this Jul 31, 2026
@Andrea-gm Andrea-gm reopened this Jul 31, 2026
@Andrea-gm

Copy link
Copy Markdown
Member

Closed upon request of @lauren-walters

@Andrea-gm Andrea-gm closed this Jul 31, 2026
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.

2 participants