File size: 3,857 Bytes
2583376
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""
Unit tests for the MD force field / solvent configuration menu.

Covers the parts that do not require OpenMM (fast, always run):
- FF_XML / SOLVENT_XML menus contain the documented entries
- get_forcefields_menu() shape and that it only exposes verified combos
- resolve_combo() normalization and explicit ValueError for unknown force
  fields, unknown solvents, and (when OpenMM is present) unverified pairs

The heavy per-combo createSystem() probe is exercised once (skip-if-no-OpenMM)
to confirm the startup verification itself runs end to end.
"""

import pytest

from app.tools.md_config import (
    FF_XML,
    SOLVENT_XML,
    DEFAULT_FORCEFIELD,
    DEFAULT_SOLVENT,
    get_forcefields_menu,
    resolve_combo,
    verify_ff_solvent_combos,
)


class TestMenus:
    def test_forcefield_menu_entries(self):
        assert FF_XML == {
            "amber14": "amber14-all.xml",
            "ff14sb": "amber14/protein.ff14SB.xml",
            "ff15ipq": "amber14/protein.ff15ipq.xml",
            "ff19sb": "amber19/protein.ff19SB.xml",
            "amberfb15": "amberfb15.xml",
            "charmm36": "charmm36.xml",
        }

    def test_solvent_menu_entries(self):
        assert SOLVENT_XML == {
            "obc1": "implicit/obc1.xml",
            "obc2": "implicit/obc2.xml",
            "gbn2": "implicit/gbn2.xml",
        }

    def test_defaults_are_amber14_obc2(self):
        assert DEFAULT_FORCEFIELD == "amber14"
        assert DEFAULT_SOLVENT == "obc2"


class TestGetForcefieldsMenu:
    def test_menu_shape(self):
        menu = get_forcefields_menu()
        assert set(menu) >= {"forcefields", "solvents", "combos", "defaults"}
        assert isinstance(menu["forcefields"], list)
        assert isinstance(menu["solvents"], list)
        assert menu["defaults"] == {"forcefield": "amber14", "solvent": "obc2"}
        # every listed force field has at least one verified solvent
        combos = menu["combos"]
        for ff in menu["forcefields"]:
            assert ff["value"] in combos
            assert combos[ff["value"]]

    def test_combos_only_contain_known_solvents(self):
        menu = get_forcefields_menu()
        for ff, solvents in menu["combos"].items():
            assert ff in FF_XML
            assert set(solvents) <= set(SOLVENT_XML)


class TestResolveCombo:
    def test_defaults(self):
        assert resolve_combo(None, None) == ("amber14", "obc2")

    def test_normalization(self):
        assert resolve_combo(" ff14SB ", "GBN2") == ("ff14sb", "gbn2")

    def test_unknown_forcefield_raises(self):
        with pytest.raises(ValueError, match="Unsupported force field"):
            resolve_combo("opls", "obc2")

    def test_unknown_solvent_raises(self):
        with pytest.raises(ValueError, match="Unsupported solvent"):
            resolve_combo("ff14sb", "explicit")

    def test_unverified_combo_raises(self):
        # Simulate a deployment where only amber14 passed verification.
        verified = verify_ff_solvent_combos()
        if not verified:
            pytest.skip("OpenMM unavailable — verification empty")
        unverified = [ff for ff in FF_XML if ff not in verified]
        if not unverified:
            pytest.skip("all force fields verified — nothing unverified to test")
        with pytest.raises(ValueError, match="not available"):
            resolve_combo(unverified[0], "obc2")


class TestStartupVerification:
    def test_verification_runs_end_to_end(self):
        verified = verify_ff_solvent_combos()
        if not verified:
            pytest.importorskip("openmm")
        # Every entry maps to a valid force field with known solvents.
        assert verified == {ff: tuple(s) for ff, s in verified.items()}
        for ff, solvents in verified.items():
            assert ff in FF_XML
            assert set(solvents) <= set(SOLVENT_XML)