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
38 changes: 35 additions & 3 deletions dpgen2/exploration/task/caly_task_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ def set_params(
self.numb_of_species = numb_of_species
self.numb_of_atoms = numb_of_atoms

if numb_of_species != len(name_of_atoms):
raise ValueError(
f"numb_of_species={numb_of_species} does not match "
f"name_of_atoms={name_of_atoms!r}"
)
for atoms in name_of_atoms:
for atom in atoms if isinstance(atoms, list) else [atoms]:
if atom not in atomic_number_map or atom == "X":
raise ValueError(
f"unknown element {atom!r} in name_of_atoms={name_of_atoms!r}"
)

if isinstance(name_of_atoms, list) and all(
[isinstance(i, list) for i in name_of_atoms]
):
Expand Down Expand Up @@ -144,12 +156,32 @@ def set_params(
self.atomic_number = [atomic_symbols.index(i) for i in self.name_of_atoms]
else:
self.name_of_atoms = name_of_atoms
self.atomic_number = atomic_number
self.atomic_number = (
[atomic_number_map[name] for name in self.name_of_atoms]
if atomic_number is None
else atomic_number
)

if isinstance(distance_of_ions, dict):
if distance_of_ions is None or isinstance(distance_of_ions, dict):
# Generate a complete distance matrix from the maintained
# covalent-radius table when the optional matrix is omitted.
updated_table = copy.deepcopy(covalent_radii)
for key, value in distance_of_ions.items():
overrides = distance_of_ions if isinstance(distance_of_ions, dict) else {}
for key, value in overrides.items():
if key not in atomic_number_map or key == "X":
raise ValueError(
f"unknown element {key!r} in distance_of_ions overrides"
)
updated_table[atomic_number_map[key]] = value
for atom in self.name_of_atoms:
if (
covalent_radii[atomic_number_map[atom]] == UNKN
and atom not in overrides
):
raise ValueError(
f"no known covalent radius for {atom!r}; provide an explicit "
"distance_of_ions matrix or a radius override for this element"
)

temp_distance_mtx = np.zeros((numb_of_species, numb_of_species))
for i in range(numb_of_species):
Expand Down
10 changes: 8 additions & 2 deletions dpgen2/exploration/task/make_task_group_from_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ def caly_task_grp_args():
"atomic_number",
list,
optional=True,
doc="atomic number of each element.",
doc="Atomic number of each element. Inferred from name_of_atoms when omitted.",
),
Argument(
"numb_of_atoms",
Expand All @@ -394,7 +394,13 @@ def caly_task_grp_args():
"distance_of_ions",
[list, dict],
optional=True,
doc="the distance matrix between different elements.",
doc=(
"Minimum interatomic distance matrix in angstrom. When omitted, "
"each entry is 0.7 times the sum of the two Cordero covalent radii, "
"rounded to two decimal places. A dict overrides radii by element "
"before computing the matrix. Elements without a known radius "
"require an explicit matrix or a radius override."
),
),
Argument(
"pop_size",
Expand Down
102 changes: 102 additions & 0 deletions tests/exploration/test_caly_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""Numerical defaults and explicit values in CALYPSO task generation."""

import unittest

import numpy as np

from dpgen2.exploration.task import (
make_calypso_task_group_from_config,
)


class TestCalyDefaults(unittest.TestCase):
def setUp(self):
self.config = {
"name_of_atoms": ["Li", "La"],
"numb_of_atoms": [10, 10],
"numb_of_species": 2,
}

def test_infers_optional_atomic_numbers_and_distances(self):
"""Pin physical values, including the serialized CALYPSO input."""
group = make_calypso_task_group_from_config(self.config)
self.assertEqual(group.atomic_number, [3, 57])
np.testing.assert_allclose(group.distance_of_ions, [[1.79, 2.34], [2.34, 2.90]])
tasks = group.make_task()
self.assertEqual(len(tasks), 1)
contents = tasks[0].files()["input.dat"]
self.assertIn("AtomicNumber = 3 57", contents)
self.assertIn("@DistanceOfIon\n1.79 2.34\n2.34 2.9\n@End", contents)

def test_radius_override_changes_only_the_requested_element(self):
group = make_calypso_task_group_from_config(
dict(self.config, distance_of_ions={"Li": 2.0})
)
self.assertEqual(group.atomic_number, [3, 57])
np.testing.assert_allclose(group.distance_of_ions, [[2.8, 2.85], [2.85, 2.9]])
self.assertEqual(len(group.make_task()), 1)
# Overrides must not mutate the shared default-radius table.
defaults = make_calypso_task_group_from_config(self.config)
np.testing.assert_allclose(
defaults.distance_of_ions, [[1.79, 2.34], [2.34, 2.9]]
)

def test_each_optional_field_can_be_omitted_independently(self):
matrix = [[2.0, 3.0], [3.0, 4.0]]
group = make_calypso_task_group_from_config(
dict(self.config, distance_of_ions=matrix)
)
self.assertEqual(group.atomic_number, [3, 57])
np.testing.assert_allclose(group.distance_of_ions, matrix)
self.assertEqual(len(group.make_task()), 1)
group = make_calypso_task_group_from_config(
dict(self.config, atomic_number=[3, 57])
)
self.assertEqual(group.atomic_number, [3, 57])
np.testing.assert_allclose(group.distance_of_ions, [[1.79, 2.34], [2.34, 2.9]])
self.assertEqual(len(group.make_task()), 1)

def test_explicit_lists_are_preserved(self):
matrix = [[2.0, 3.0], [3.0, 4.0]]
group = make_calypso_task_group_from_config(
dict(self.config, atomic_number=[3, 57], distance_of_ions=matrix)
)
self.assertEqual(group.atomic_number, [3, 57])
np.testing.assert_allclose(group.distance_of_ions, matrix)

def test_species_count_reports_both_inputs(self):
with self.assertRaisesRegex(ValueError, "numb_of_species=3.*name_of_atoms"):
make_calypso_task_group_from_config(dict(self.config, numb_of_species=3))

def test_unknown_element_reports_the_configuration_key(self):
for names in (["Li", "Xx"], ["Li", "X"], [["Li", "Xx"], ["La"]]):
with self.subTest(names=names):
with self.assertRaisesRegex(
ValueError, "unknown element.*name_of_atoms"
):
make_calypso_task_group_from_config(
dict(self.config, name_of_atoms=names)
)
with self.assertRaisesRegex(ValueError, "unknown element.*distance_of_ions"):
make_calypso_task_group_from_config(
dict(self.config, distance_of_ions={"Xx": 2.0})
)

def test_unknown_radius_requires_an_explicit_value(self):
config = {"name_of_atoms": ["Cf"], "numb_of_atoms": [1], "numb_of_species": 1}
for distance in (None, {}):
with self.subTest(distance=distance):
with self.assertRaisesRegex(ValueError, "no known covalent radius.*Cf"):
make_calypso_task_group_from_config(
config
if distance is None
else dict(config, distance_of_ions=distance)
)
for distance, expected in (({"Cf": 2.0}, [[2.8]]), ([[3.0]], [[3.0]])):
with self.subTest(distance=distance):
group = make_calypso_task_group_from_config(
dict(config, distance_of_ions=distance)
)
self.assertEqual(group.atomic_number, [98])
np.testing.assert_allclose(group.distance_of_ions, expected)
self.assertEqual(len(group.make_task()), 1)
Loading