Skip to content

Commit 59f74e1

Browse files
committed
Explicit hydrogen removal parity #67
1 parent ffcf3e0 commit 59f74e1

9 files changed

Lines changed: 128 additions & 42 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ supplied in full. Options may appear before or after the input and use
171171
| `--accept-palindromes=<0\|1>` | `0` | Identify a string fragment with its reversal. |
172172
| `--parallel=<auto\|on\|off>` | `off` | Select parallel search automatically, require it, or disable it. |
173173
| `--threads=<auto\|N>` | `auto` | Set the OpenMP thread count per process; `N` must be positive. |
174-
| `--remove-hydrogens=<0\|1>` | `1` | Remove explicit hydrogens from molfiles. |
174+
| `--remove-hydrogens=<0\|1>` | `1` | Remove explicit hydrogens from MOL/SDF and native graph inputs. |
175175
| `--verbose=<0\|1>` | `0` | Print the parsed input graph. |
176176
| `--compensate-disjoint=<0\|1>` | `0` | Subtract one per processed component after the first. |
177177
| `--memory-report=<0\|1>` | `0` | Write Linux peak virtual memory to `memUsage`. |

‎unitTests/graphioTester.cpp‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ namespace
120120

121121
int main()
122122
{
123+
removeHydrogens = true;
123124
verbose = false;
124125

125126
molGraph graph = parse(validGraph);
@@ -135,6 +136,39 @@ int main()
135136
assert(graph.atoms[1].bonds[0].bondType == 1);
136137
assert(graph.atoms[1].bonds[1].bondType == 2);
137138

139+
const string explicitHydrogenGraph =
140+
"explicit hydrogens\n"
141+
"4\n"
142+
"1 3 2 3 3 4\n"
143+
"H H C C\n"
144+
"1 1 1\n";
145+
const molGraph hydrogensRemoved = parse(explicitHydrogenGraph);
146+
assert(hydrogensRemoved.atoms.size() == 2);
147+
assert(hydrogensRemoved.totalBonds == 1);
148+
assert(hydrogensRemoved.atoms[0].atomType == "C");
149+
assert(hydrogensRemoved.atoms[1].atomType == "C");
150+
assert(hydrogensRemoved.atoms[0].bonds[0].neighbourAtomIndex == 1);
151+
152+
removeHydrogens = false;
153+
const molGraph hydrogensRetained = parse(explicitHydrogenGraph);
154+
assert(hydrogensRetained.atoms.size() == 4);
155+
assert(hydrogensRetained.totalBonds == 3);
156+
assert(hydrogensRetained.atoms[0].atomType == "H");
157+
assert(hydrogensRetained.atoms[1].atomType == "H");
158+
159+
removeHydrogens = true;
160+
const molGraph unrestrictedLabels = parse(
161+
"unrestricted labels\n"
162+
"2\n"
163+
"1 2\n"
164+
"COLLAPSE He\n"
165+
"1\n"
166+
);
167+
assert(unrestrictedLabels.atoms.size() == 2);
168+
assert(unrestrictedLabels.totalBonds == 1);
169+
assert(unrestrictedLabels.atoms[0].atomType == "COLLAPSE");
170+
assert(unrestrictedLabels.atoms[1].atomType == "He");
171+
138172
molGraph replacementTarget = sentinelGraph();
139173
istringstream validInput(validGraph);
140174
graphio(validInput, replacementTarget);

‎unitTests/libraryBatchTester.cpp‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,30 @@ int main(int argc, char **argv)
9191
)
9292
) return 1;
9393

94+
const std::string explicitHydrogenGraph =
95+
"explicit hydrogens\n"
96+
"6\n"
97+
"1 3 2 3 3 4 4 5 4 6\n"
98+
"H H C C H H\n"
99+
"1 1 1 1 1\n";
100+
std::istringstream filteredGraphStream(explicitHydrogenGraph);
101+
const assemblycpp::CalculationResult filteredGraph =
102+
assemblycpp::calculateGraph(filteredGraphStream);
103+
assemblycpp::CalculationOptions retainedHydrogenOptions;
104+
retainedHydrogenOptions.removeHydrogens = false;
105+
std::istringstream retainedGraphStream(explicitHydrogenGraph);
106+
const assemblycpp::CalculationResult retainedGraph =
107+
assemblycpp::calculateGraph(
108+
retainedGraphStream,
109+
retainedHydrogenOptions
110+
);
111+
if (
112+
!require(filteredGraph.succeeded, "filtered graph calculation failed") ||
113+
!require(filteredGraph.assemblyIndex == 0, "filtered graph index mismatch") ||
114+
!require(retainedGraph.succeeded, "retained graph calculation failed") ||
115+
!require(retainedGraph.assemblyIndex == 3, "retained graph index mismatch")
116+
) return 1;
117+
94118
std::istringstream invalidGraph(
95119
"invalid graph\n2\n1 3\nC C\n1\n"
96120
);

‎unitTests/unitTester.py‎

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,7 @@ def run_cli_checks(executable: Path) -> int:
12751275
5,
12761276
),
12771277
)
1278+
molecular_hydrogen_graphs = {}
12781279
for (
12791280
name,
12801281
hydrogen_options,
@@ -1309,20 +1310,43 @@ def run_cli_checks(executable: Path) -> int:
13091310
"atoms or bonds",
13101311
completed,
13111312
)
1313+
molecular_hydrogen_graphs[tuple(hydrogen_options)] = graph
13121314
scenarios += 1
13131315

1314-
native_hydrogen_graph = "native-hydrogens\n4\n1 3 2 3 3 4\nH H C C\n1 1 1\n"
1315-
expected_native_graph = None
1316-
for name, hydrogen_option in (
1317-
("native-hydrogens-on", "--remove-hydrogens=1"),
1318-
("native-hydrogens-off", "--remove-hydrogens=0"),
1319-
):
1316+
native_hydrogen_graph = (
1317+
"native-hydrogens\n"
1318+
"6\n"
1319+
"1 3 2 3 3 4 4 5 4 6\n"
1320+
"H H C C H H\n"
1321+
"1 1 1 1 1\n"
1322+
)
1323+
native_hydrogen_cases = (
1324+
("native-hydrogens-default", [], ["C", "C"], 1),
1325+
(
1326+
"native-hydrogens-on",
1327+
["--remove-hydrogens=1"],
1328+
["C", "C"],
1329+
1,
1330+
),
1331+
(
1332+
"native-hydrogens-off",
1333+
["--remove-hydrogens=0"],
1334+
["H", "H", "C", "C", "H", "H"],
1335+
5,
1336+
),
1337+
)
1338+
for (
1339+
name,
1340+
hydrogen_options,
1341+
expected_colours,
1342+
expected_edge_count,
1343+
) in native_hydrogen_cases:
13201344
case_directory = working_directory / name
13211345
case_directory.mkdir()
13221346
(case_directory / "input").write_text(native_hydrogen_graph)
13231347
completed = run_cli_command(
13241348
executable,
1325-
["input", "--pathway=1", hydrogen_option],
1349+
["input", "--pathway=1", *hydrogen_options],
13261350
case_directory,
13271351
)
13281352
require_cli(
@@ -1338,20 +1362,18 @@ def run_cli_checks(executable: Path) -> int:
13381362
)
13391363
graph = parse_pathway_document(pathway_path)["file_graph"][0]
13401364
require_cli(
1341-
graph["VertexColours"] == ["H", "H", "C", "C"]
1342-
and len(graph["Edges"]) == 3,
1343-
"--remove-hydrogens should not transform native graph inputs",
1365+
graph["VertexColours"] == expected_colours
1366+
and len(graph["Edges"]) == expected_edge_count,
1367+
f"native-graph hydrogen scenario {name!r} transformed the "
1368+
"wrong atoms or bonds",
1369+
completed,
1370+
)
1371+
require_cli(
1372+
graph == molecular_hydrogen_graphs[tuple(hydrogen_options)],
1373+
f"native-graph hydrogen scenario {name!r} should match its "
1374+
"MOL equivalent",
13441375
completed,
13451376
)
1346-
if expected_native_graph is None:
1347-
expected_native_graph = graph
1348-
else:
1349-
require_cli(
1350-
graph == expected_native_graph,
1351-
"native graph output should be identical with hydrogen "
1352-
"removal on or off",
1353-
completed,
1354-
)
13551377
scenarios += 1
13561378

13571379
all_hydrogen_mol = (

‎v5/assemblycpp.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct CalculationOptions
2424
{
2525
std::uint64_t runtimeTicks = std::numeric_limits<std::uint64_t>::max();
2626
int enumerationLimit = 50000000;
27+
/** Remove explicit H vertices from MOL/SDF and native graph inputs. */
2728
bool removeHydrogens = true;
2829
bool compensateDisjoint = false;
2930
bool verbose = false;

‎v5/graphio.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ inline void graphio(std::istream &inputStream, molGraph &molecule)
236236
);
237237
}
238238

239+
if (removeHydrogens) parsed.removeExplicitHydrogens();
239240
if (verbose)
240241
{
241242
const std::vector<std::string> nameFields = graphioDetail::tokens(nameLine);

‎v5/ioflag.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ const vector<InputFlagDefinition>& inputFlagDefinitions()
109109
"remove-hydrogens",
110110
"0|1",
111111
"1",
112-
"Remove explicit hydrogens from molfile inputs.",
112+
"Remove explicit hydrogens from MOL/SDF and native graph inputs.",
113113
{"removeHydrogens"}
114114
},
115115
{

‎v5/molGraph.h‎

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,18 @@ struct molGraph
143143

144144
private:
145145
/**
146-
* @brief Rebuild the graph while dropping removed atoms and zero-order bonds.
146+
* @brief Rebuild the graph while dropping selected atoms and zero-order bonds.
147147
*/
148-
void rebuild(bool removeMarkedAtoms)
148+
template <typename AtomRemovalPredicate>
149+
void rebuild(AtomRemovalPredicate shouldRemoveAtom)
149150
{
150151
const size_t removed = atoms.size();
151152
vector<size_t> reverseMap(atoms.size(), removed);
152153
molGraph output;
153154

154155
for (size_t i = 0; i < atoms.size(); i++)
155156
{
156-
if (removeMarkedAtoms && atoms[i].atomType == "COLLAPSE") continue;
157+
if (shouldRemoveAtom(atoms[i])) continue;
157158
reverseMap[i] = output.atoms.size();
158159
output.addAtom(atoms[i].atomType);
159160
}
@@ -190,26 +191,36 @@ struct molGraph
190191
*/
191192
void collapse()
192193
{
193-
rebuild(false);
194+
rebuild([](const atom &) { return false; });
194195
}
195196

196-
/**
197-
* @brief For explicit hydrogen removal
198-
*
199-
*/
197+
/** Mark one atom for removal by removeAndCollapse(). */
200198
void removeAtom(size_t i)
201199
{
202200
if (i >= atoms.size()) return;
203201
atoms[i].atomType = "COLLAPSE";
204202
}
205203

206-
/**
207-
* @brief For explicit hydrogen removal
208-
*
209-
*/
204+
/** Remove atoms marked by removeAtom(), then rebuild the graph. */
210205
void removeAndCollapse()
211206
{
212-
rebuild(true);
207+
rebuild(
208+
[](const atom &candidate)
209+
{
210+
return candidate.atomType == "COLLAPSE";
211+
}
212+
);
213+
}
214+
215+
/** Remove every explicitly represented hydrogen atom and its bonds. */
216+
void removeExplicitHydrogens()
217+
{
218+
rebuild(
219+
[](const atom &candidate)
220+
{
221+
return candidate.atomType == "H";
222+
}
223+
);
213224
}
214225

215226
/**

‎v5/molfileParser.h‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,7 @@ void molfileParser(std::istream &molfile, molGraph &molecule)
166166
);
167167
parsed.addBond(atomA - 1, atomB - 1, bondOrder);
168168
}
169-
if (removeHydrogens)
170-
{
171-
for (size_t atomIndex = 0; atomIndex < parsed.atoms.size(); atomIndex++)
172-
{
173-
if (parsed.atomType(atomIndex) == "H") parsed.removeAtom(atomIndex);
174-
}
175-
parsed.removeAndCollapse();
176-
}
169+
if (removeHydrogens) parsed.removeExplicitHydrogens();
177170
if (verbose) parsed.printToCout();
178171
molecule = std::move(parsed);
179172
}

0 commit comments

Comments
 (0)