Skip to content
Open
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
18 changes: 17 additions & 1 deletion dpgen2/op/run_dp_train.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,23 @@ def execute(
len_init = len(init_data)
numb_old = len_init + len(iter_data_old_exp)
numb_new = numb_old + len(iter_data_new_exp)
auto_prob_str = f"prob_sys_size; 0:{numb_old}:{old_ratio}; {numb_old}:{numb_new}:{1.-old_ratio:g}"
if numb_new > numb_old:
auto_prob_str = f"prob_sys_size; 0:{numb_old}:{old_ratio}; {numb_old}:{numb_new}:{1.-old_ratio:g}"
else:
# No new systems from the latest iteration.
# This can happen when FP labeling fails on all conformations
# and continue_on_success_ratio allows the workflow to proceed.
# Fall back to uniform prob_sys_size to avoid generating an
# empty range (e.g. "0:2:0.6; 2:2:0.4") that crashes with
# "ValueError: probabilities do not sum to 1".
auto_prob_str = "prob_sys_size"
logging.warning(
"No new systems from the latest iteration "
"(iter_data_new_exp is empty, numb_old == numb_new == %d). "
"Falling back to auto_prob='prob_sys_size'. "
"Training will proceed with init_data + old iter_data only.",
numb_old,
)

# update the input dict
train_dict = RunDPTrain.write_data_to_input_script(
Expand Down
65 changes: 65 additions & 0 deletions tests/op/test_run_dp_train.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,71 @@ def test_decide_init_model_config_larger_than_yes(self):
)
self.assertTrue(do_init_model)

def test_auto_prob_empty_new_iter_data(self):
"""Test that auto_prob falls back to 'prob_sys_size' when
iter_data_new_exp is empty (e.g., FP produced no labeled data).

Previously this would generate "prob_sys_size; 0:2:0.6; 2:2:0.4"
which crashes dp train with "probabilities do not sum to 1".

This test exercises the real RunDPTrain.execute() code path with
a mocked run_command to verify the generated training script.
"""
# Create an empty directory to simulate iter_data with no systems
empty_iter = Path("empty_iter_data")
empty_iter.mkdir(exist_ok=True)

# Create a task_path with input.json
task_path = Path("input-auto-prob-test")
task_path.mkdir(exist_ok=True)
with open(task_path / train_script_name, "w") as fp:
json.dump(self.idict_v2, fp, indent=4)

config = self.config.copy()
config["init_model_policy"] = "yes"
config["init_model_old_ratio"] = 0.6

ip = OPIO(
{
"config": config,
"task_name": "task-auto-prob",
"task_path": task_path,
"init_model": self.init_model,
"init_data": self.init_data,
"iter_data": [empty_iter],
"valid_data": None,
"optional_files": None,
"optional_parameter": {
"mixed_type": False,
"finetune_mode": "no",
},
}
)

op = RunDPTrain()
# Mock run_command so dp train is not actually invoked
with patch("dpgen2.op.run_dp_train.run_command", return_value=(0, "", "")):
try:
op.execute(ip)
except Exception:
# May fail on freeze/post-process; we only care about
# the generated training script at this point.
pass

# Read the generated training script and verify auto_prob
script_path = Path("task-auto-prob") / train_script_name
self.assertTrue(script_path.exists(), "Training script was not generated")
with open(script_path) as fp:
train_dict = json.load(fp)
auto_prob = train_dict["training"]["training_data"]["auto_prob"]
# Must be plain "prob_sys_size", NOT "prob_sys_size; 0:2:0.6; 2:2:0.4"
self.assertEqual(auto_prob, "prob_sys_size")

# Cleanup
shutil.rmtree("empty_iter_data", ignore_errors=True)
shutil.rmtree("task-auto-prob", ignore_errors=True)
shutil.rmtree("input-auto-prob-test", ignore_errors=True)

def test_update_input_dict_v1_init_model(self):
odict = RunDPTrain.write_data_to_input_script(
self.idict_v1,
Expand Down