Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
06d6ccf
added kwargs for jobflow library building
jaeminy00 Apr 9, 2026
3018d25
fixed typo
jaeminy00 Apr 9, 2026
4d7fb76
added entry_kwargs to the @job decorated setup_reaction_library as well
jaeminy00 Apr 9, 2026
9b849e8
Bayesian workflow jobs
jaeminy00 Apr 16, 2026
40c8982
added bayesian optimizer JobFlow flow file
jaeminy00 Apr 17, 2026
f80e728
Created jobs and flows folders, and separated job and flow files for …
jaeminy00 Apr 17, 2026
3e82226
Revise docstring for core jobflow jobs
jaeminy00 Apr 17, 2026
b4c7955
Refactor imports and enhance reaction library setup
jaeminy00 Apr 17, 2026
b6d9f39
Enhance docstring with example for create_simulation_flow
jaeminy00 Apr 17, 2026
9969eac
fixed kwargs not correctly being added due to wrong branching
jaeminy00 Apr 17, 2026
a4017c8
fixed typo
jaeminy00 Apr 17, 2026
aca9692
more kwargs error!!!
jaeminy00 Apr 18, 2026
8aa0d69
fixed KeyError issue arising from using floats instead of ints when s…
jaeminy00 Apr 20, 2026
e479e7a
fixed mongoDB max memory issue, now passes the path instead of the wh…
jaeminy00 Apr 20, 2026
843add8
fixed flows/bayesian.py as well
jaeminy00 Apr 20, 2026
10ca6bf
made changes to kwargs logic to make sure everything is passed down c…
jaeminy00 Apr 21, 2026
db00955
fixed a bug where json from 1 trial to another was not writing correctly
jaeminy00 May 1, 2026
575339e
deleted the weird json file
jaeminy00 May 1, 2026
9c4f68e
Change campaign_json to file path for next trial
jaeminy00 May 1, 2026
7db9951
cherry picked max's edits on precursor selection
jaeminy00 May 1, 2026
99488a8
accounting for when job walltime hits and need to re-launch on a new …
jaeminy00 May 4, 2026
8b07d00
Enable optimization of precursor amounts with ratios
jaeminy00 May 6, 2026
36955a6
fixed fireworks saving launcher files to wrong directories
May 7, 2026
7b24565
changes to make sure the correct job is picked up by fireworks; modified
May 24, 2026
e3e7120
jobflow job naming convention fixes
May 26, 2026
a1463e4
Fix Ray over-subscription deadlock on SLURM shared nodes
jaeminy00 May 26, 2026
cefba4e
changed default compress_freq to 500, instead of 1, for BO workflows …
jaeminy00 May 27, 2026
15bc4c3
changed default metastability cutoff from 0.1 to 0.03
jaeminy00 May 29, 2026
6cca609
Removed the Ray initialization
jaeminy00 Jun 2, 2026
b7237e1
Removed Ray Initialization line as it's not functional
jaeminy00 Jun 2, 2026
e06c6db
Merge remote-tracking branch 'refs/remotes/origin/master'
jaeminy00 Jun 2, 2026
74bcf32
test
jaeminy00 Jun 2, 2026
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
30 changes: 29 additions & 1 deletion src/rxn_ca/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,15 +293,43 @@ def suggest_precursors():
action='store_true',
help='Output as JSON instead of human-readable format',
)
parser.add_argument(
'--anions',
type=str,
default=None,
help='Comma-separated anion formulas for precursors (default: O,CO3,OH,NO3)',
)
parser.add_argument(
'--metathesis',
type=str,
default=None,
help='Comma-separated metathesis anion formulas (e.g., Cl or Cl,Br)',
)
parser.add_argument(
'--counter-cations',
type=str,
default=None,
help='Comma-separated counter-cations for metathesis (e.g., Na,K)',
)

args = parser.parse_args()

target = args.target
print(f"Finding precursor combinations for: {target}", file=sys.stderr)

# Parse anion/cation arguments
anions = args.anions.split(',') if args.anions else None
metathesis_anions = args.metathesis.split(',') if args.metathesis else None
counter_cations = args.counter_cations.split(',') if args.counter_cations else None

# Expand elements to include precursor anions (C, N, H, etc.)
print("Expanding element set for precursor phases...", file=sys.stderr)
elements = get_expanded_elements(target)
elements = get_expanded_elements(
target,
anions=anions,
metathesis_anions=metathesis_anions,
counter_cations=counter_cations,
)
print(f" Elements: {', '.join(sorted(elements))}", file=sys.stderr)

# Fetch entries from Materials Project
Expand Down
2 changes: 1 addition & 1 deletion src/rxn_ca/optimization/objective.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ObjectiveConfig:
num_realizations: int = 3
cache_results: bool = True
live_compress: bool = True
compress_freq: int = 50
compress_freq: int = 500


class ObjectiveFunction:
Expand Down
131 changes: 89 additions & 42 deletions src/rxn_ca/optimization/precursor_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ def __post_init__(self):
AnionType("phosphate", "PO4", -3, frozenset({"P", "O"})),
]

# Default anion types for typical solid-state synthesis
DEFAULT_PRECURSOR_ANIONS: List[str] = ["oxide", "carbonate", "hydroxide", "nitrate"]
# Default anion types for typical solid-state synthesis (by formula)
DEFAULT_PRECURSOR_ANIONS: List[str] = ["O", "CO3", "OH", "NO3"]

# Anion types useful for metathesis reactions
METATHESIS_ANIONS: List[str] = ["chloride", "bromide", "nitrate", "sulfate", "acetate"]
# Anion types useful for metathesis reactions (by formula)
METATHESIS_ANIONS: List[str] = ["Cl", "Br", "NO3", "SO4", "C2H3O2"]

# Counter-cations for metathesis reactions (provide leaving groups)
# Maps cation symbol to its oxidation state
Expand All @@ -81,6 +81,9 @@ def __post_init__(self):
"Cs": 1,
}

# Build formula -> AnionType lookup (populated after COMMON_ANION_TYPES is defined)
_ANION_BY_FORMULA: Dict[str, "AnionType"] = {}


# =============================================================================
# Precursor formula generation
Expand Down Expand Up @@ -164,22 +167,49 @@ def generate_precursor_formula(
return cation_str + anion_str


def get_anion_by_name(name: str) -> AnionType:
"""Get an AnionType by its name.
def _build_anion_lookup() -> None:
"""Build the formula -> AnionType lookup dict."""
for anion in COMMON_ANION_TYPES:
_ANION_BY_FORMULA[anion.formula] = anion


def get_anion(identifier: str) -> AnionType:
"""Get an AnionType by its formula or name.

Args:
name: Anion name (e.g., "oxide", "carbonate")
identifier: Anion formula (e.g., "CO3", "Cl") or name (e.g., "carbonate")

Returns:
Matching AnionType

Raises:
ValueError: If anion name not found
ValueError: If anion not found

Examples:
>>> get_anion("CO3")
AnionType(name='carbonate', formula='CO3', ...)
>>> get_anion("Cl")
AnionType(name='chloride', formula='Cl', ...)
>>> get_anion("carbonate") # name also works for backwards compat
AnionType(name='carbonate', formula='CO3', ...)
"""
# Ensure lookup is populated
if not _ANION_BY_FORMULA:
_build_anion_lookup()

# Try formula first
if identifier in _ANION_BY_FORMULA:
return _ANION_BY_FORMULA[identifier]

# Fall back to name lookup for backwards compatibility
for anion in COMMON_ANION_TYPES:
if anion.name == name:
if anion.name == identifier:
return anion
raise ValueError(f"Unknown anion type: {name}")

raise ValueError(
f"Unknown anion: '{identifier}'. "
f"Valid formulas: {list(_ANION_BY_FORMULA.keys())}"
)


def generate_practical_precursors(
Expand Down Expand Up @@ -214,7 +244,7 @@ def generate_practical_precursors(
if not oxidation_states:
return []

anions = [get_anion_by_name(name) for name in anion_types]
anions = [get_anion(a) for a in anion_types]

precursors = []
seen = set()
Expand Down Expand Up @@ -253,7 +283,7 @@ def generate_metathesis_sources(
if counter_cations is None:
counter_cations = ["Na", "K"]

anion = get_anion_by_name(target_anion)
anion = get_anion(target_anion)

sources = []
for cation in counter_cations:
Expand All @@ -266,73 +296,90 @@ def generate_metathesis_sources(
return sources


def get_elements_from_anion_types(anion_types: Optional[List[str]] = None) -> Set[str]:
"""Get all elements introduced by a set of anion types.
def get_elements_from_anions(anions: Optional[List[str]] = None) -> Set[str]:
"""Get all elements introduced by a set of anions.

Args:
anion_types: List of anion type names. Defaults to DEFAULT_PRECURSOR_ANIONS.
anions: List of anion formulas (e.g., ["CO3", "Cl"]).
Defaults to DEFAULT_PRECURSOR_ANIONS.

Returns:
Set of element symbols
"""
if anion_types is None:
anion_types = DEFAULT_PRECURSOR_ANIONS
if anions is None:
anions = DEFAULT_PRECURSOR_ANIONS

elements: Set[str] = set()
for name in anion_types:
anion = get_anion_by_name(name)
for identifier in anions:
anion = get_anion(identifier)
elements.update(anion.elements)

return elements


def get_expanded_elements(
target_phase: str,
anion_types: Optional[List[str]] = None,
include_metathesis: bool = True,
anions: Optional[List[str]] = None,
metathesis_anions: Optional[List[str]] = None,
counter_cations: Optional[List[str]] = None,
) -> Set[str]:
"""Get the full set of elements needed for precursor selection.

This expands beyond the target phase elements to include elements from
common precursor anions (e.g., C from carbonates, N from nitrates).
precursor anions (e.g., C from CO3, N from NO3) and optionally metathesis
reagents.

Use this to determine what elements to pass to get_entries().

Args:
target_phase: Target product formula (e.g., "BaTiO3")
anion_types: Anion types to consider. Defaults to DEFAULT_PRECURSOR_ANIONS.
include_metathesis: If True, also include elements from metathesis anions.
anions: Base anion formulas for precursors. Defaults to ["O", "CO3", "OH", "NO3"].
metathesis_anions: Additional anions for metathesis (e.g., ["Cl"]).
Defaults to None (no metathesis anions).
counter_cations: Counter-cations for metathesis (e.g., ["Na", "K"]).
Defaults to None (no counter-cations).

Returns:
Set of element symbols needed for get_entries()

Examples:
>>> get_expanded_elements("BaTiO3")
{'Ba', 'Ti', 'O', 'C', 'N', 'H'}
>>> get_expanded_elements("BaTiO3", anion_types=["oxide"])
>>> get_expanded_elements("BaTiO3", anions=["O"])
{'Ba', 'Ti', 'O'}
>>> get_expanded_elements("BaTiO3", metathesis_anions=["Cl"], counter_cations=["Na"])
{'Ba', 'Ti', 'O', 'C', 'N', 'H', 'Cl', 'Na'}
"""
# Start with target phase elements
target_comp = Composition(target_phase)
elements = {str(el) for el in target_comp.elements}

# Add elements from precursor anions
if anion_types is None:
anion_types = list(DEFAULT_PRECURSOR_ANIONS)

if include_metathesis:
# Add metathesis anion elements too
anion_types = list(set(anion_types + METATHESIS_ANIONS))

elements.update(get_elements_from_anion_types(anion_types))

# Add counter-cation elements if including metathesis
if include_metathesis:
elements.update(METATHESIS_COUNTER_CATIONS.keys())
# Remove NH4 as it's not a real element, add N and H instead
elements.discard("NH4")
elements.add("N")
elements.add("H")
# Add elements from base precursor anions
if anions is None:
anions = list(DEFAULT_PRECURSOR_ANIONS)

all_anions = list(anions)

# Add metathesis anions if specified
if metathesis_anions:
all_anions = list(set(all_anions + metathesis_anions))

elements.update(get_elements_from_anions(all_anions))

# Add counter-cation elements if specified
if counter_cations:
for cation in counter_cations:
if cation == "NH4":
# NH4 is not a real element, add N and H instead
elements.add("N")
elements.add("H")
elif cation not in METATHESIS_COUNTER_CATIONS:
raise ValueError(
f"Unknown counter-cation: '{cation}'. "
f"Valid options: {list(METATHESIS_COUNTER_CATIONS.keys())}"
)
else:
elements.add(cation)

return elements

Expand Down
50 changes: 46 additions & 4 deletions src/rxn_ca/optimization/search_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,26 @@ def add_precursor_ratio(
name: str,
low: float,
high: float,
step: float = 0.05,
) -> "SearchSpace":
"""Add a precursor ratio parameter.
"""Add a precursor ratio parameter (discrete with given step size).

Discrete rather than continuous: BayBE enumerates all candidate values
and selects via Thompson Sampling, avoiding the L-BFGS-B boundary-hang
that occurs when a continuous parameter's optimum sits at its lower bound
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Just out of interest: was that boundary hang thing a problem? Did you resolve it somehow by using this Thompson Sampling?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I think I need to do more testing on this; making precursor ratio as a discrete variable was a semi-temporary fix and I was planning on looking at it more, but after talking to KP she said it'd be okay to not touch precursor ratios at all for now.

(e.g. the stoichiometric ratio is also the minimum of the search range).

Args:
name: Parameter name (should match a precursor slot name + "_ratio")
low: Minimum ratio (typically 0-1)
high: Maximum ratio (typically 0-1)
low: Minimum ratio value
high: Maximum ratio value
step: Grid spacing between ratio values (default 0.05)

Returns:
self for method chaining
"""
ratio_name = f"{name}_ratio" if not name.endswith("_ratio") else name
param = ContinuousParameter(name=ratio_name, low=low, high=high)
param = DiscreteParameter(name=ratio_name, low=low, high=high, step=step)
return self._add_parameter(param)

def add_continuous(
Expand Down Expand Up @@ -295,6 +302,41 @@ def parameter_names(self) -> List[str]:
"""Get list of all parameter names."""
return [p.name for p in self.parameters]

def as_dict(self) -> dict:
"""Serialize to a JSON-safe dict for passing between jobflow jobs."""
params = []
for p in self.parameters:
d: Dict[str, Any] = {"type": p.param_type.value, "name": p.name}
if isinstance(p, DiscreteParameter):
d["low"] = p.low
d["high"] = p.high
d["step"] = p.step
elif isinstance(p, ContinuousParameter):
d["low"] = p.low
d["high"] = p.high
elif isinstance(p, PrecursorSlotParameter):
d["candidates"] = p.candidates
elif isinstance(p, CategoricalParameter):
d["choices"] = p.choices
params.append(d)
return {"parameters": params}

@classmethod
def from_dict(cls, d: dict) -> "SearchSpace":
"""Reconstruct a SearchSpace from a serialized dict."""
space = cls()
for p in d["parameters"]:
ptype = p["type"]
if ptype == "discrete":
space.add_discrete(p["name"], p["low"], p["high"], p["step"])
elif ptype == "continuous":
space.add_continuous(p["name"], p["low"], p["high"])
elif ptype == "precursor_slot":
space.add_precursor_slot(p["name"], p["candidates"])
elif ptype == "categorical":
space.add_categorical(p["name"], p["choices"])
return space

def __repr__(self) -> str:
param_strs = []
for p in self.parameters:
Expand Down
13 changes: 10 additions & 3 deletions src/rxn_ca/utilities/get_scored_rxns.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import os
import multiprocessing as mp
from typing import List

from rxn_network.reactions.reaction_set import ReactionSet

from ..core import HeatingSchedule
Expand All @@ -6,11 +10,14 @@
from ..reactions import ReactionLibrary, ScoredReaction, ScoredReactionSet, score_rxns
from ..reactions.scorers import BasicScore, TammanScore

from typing import List
_scoring_globals = {}

import multiprocessing as mp

_scoring_globals = {}
def _pool_initializer(data: dict):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks, this is a lot better than the naked global declaration lol

"""Populate worker-process globals (called by forkserver/spawn pool workers)."""
global _scoring_globals
_scoring_globals.update(data)


def fn(temp):
score_class = _scoring_globals.get('score_class')
Expand Down
7 changes: 5 additions & 2 deletions src/rxn_ca/workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
"""

from .schemas import SimulationOutput, ReactionLibraryData
from .jobs import setup_reaction_library, run_simulation
from .flows import create_simulation_flow
from .jobs import setup_reaction_library, run_simulation, init_bo_campaign, bo_trial_step
from .flows import create_simulation_flow, BOFlowMaker

__all__ = [
"SimulationOutput",
"ReactionLibraryData",
"setup_reaction_library",
"run_simulation",
"init_bo_campaign",
"bo_trial_step",
"create_simulation_flow",
"BOFlowMaker",
]
Loading