diff --git a/pyproject.toml b/pyproject.toml index a6562ce..32b461f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ target-version = ["py310"] [tool.pytest.ini_options] markers = [ # Models markers - "UNIFAC: classic UNIFAC model tests", - "PSRK: predictive SRK model tests", - "Joback: Joback-Reid contribution model tests", + "unifac: classic UNIFAC model tests", + "psrk: predictive SRK model tests", + "joback: Joback-Reid contribution model tests", # Tests cases markers "acids: tests cases for acids", "alcohols: tests cases for alcohols", diff --git a/tests/cases/epoxides.py b/tests/cases/epoxides.py index 269d8e4..27dacf4 100644 --- a/tests/cases/epoxides.py +++ b/tests/cases/epoxides.py @@ -11,7 +11,10 @@ cases_module="epoxides", r=None, q=None, - unifac_result={"CH3": 1, "CH": 1, "CH2O": 1}, + unifac_result=[ + {"CH3": 1, "CH": 1, "CH2O": 1}, + {"CH3": 1, "CH2": 1, "CHO": 1}, + ], psrk_result={"CH3": 1, "H2COCH": 1}, joback_result={ "-CH3": 1, diff --git a/tests/cases/ethers.py b/tests/cases/ethers.py index f82fb53..e26720f 100644 --- a/tests/cases/ethers.py +++ b/tests/cases/ethers.py @@ -45,7 +45,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH2O": 1, "CH": 4, "OH": 4}, + unifac_result=[{'CH2': 1, 'CH': 3, 'OH': 4, 'CHO': 1}, {'CH': 4, 'OH': 4, 'CH2O': 1}], psrk_result={"CH2O": 1, "CH": 4, "OH": 4}, joback_result={ "ring-CH2-": 1, @@ -171,7 +171,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH2": 8, "CH": 1, "CH2O": 1, "CHO": 1}, + unifac_result=[{'CH2': 8, 'CH': 1, 'CH2O': 1, 'CHO': 1}, {'CH2': 9, 'CHO': 2}], psrk_result={"CH2": 8, "CH": 1, "CH2O": 1, "CHO": 1}, joback_result={ "ring-CH2-": 9, @@ -270,7 +270,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 4, "CH2": 2, "CH": 4, "COO": 1, "CH2O": 1}, + unifac_result=[{'CH3': 4, 'CH2': 2, 'CH': 4, 'CH2O': 1, 'COO': 1}, {'CH3': 4, 'CH2': 3, 'CH': 3, 'CHO': 1, 'COO': 1}], psrk_result={"CH3": 4, "CH2": 2, "CH": 4, "COO": 1, "CH2O": 1}, joback_result={ "-CH3": 4, @@ -333,7 +333,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 2, "CH": 1, "COO": 1, "CH3O": 1}, + unifac_result=[{'CH3': 3, 'CHO': 1, 'COO': 1}, {'CH3': 2, 'CH': 1, 'CH3O': 1, 'COO': 1}], psrk_result={"CH3": 2, "CH": 1, "COO": 1, "CH3O": 1}, joback_result={ "-CH3": 3, @@ -348,7 +348,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 1, "CH2": 1, "COO": 1, "CH3O": 1}, + unifac_result=[{'CH3': 2, 'CH2O': 1, 'COO': 1}, {'CH3': 1, 'CH2': 1, 'CH3O': 1, 'COO': 1}], psrk_result={"CH3": 1, "CH2": 1, "COO": 1, "CH3O": 1}, joback_result={ "-CH3": 2, @@ -373,7 +373,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3O": 2, "CH2O": 2, "CH": 1, "CH3": 1}, + unifac_result=[{'CH3': 1, 'CH': 1, 'CH3O': 2, 'CH2O': 2}, {'CH3': 2, 'CH3O': 1, 'CH2O': 2, 'CHO': 1}, {'CH3': 1, 'CH2': 1, 'CH3O': 2, 'CH2O': 1, 'CHO': 1}], psrk_result={"CH3O": 2, "CH2O": 2, "CH": 1, "CH3": 1}, joback_result={"-CH3": 3, "-CH2-": 2, ">CH-": 1, "-O- (non-ring)": 4}, ), @@ -383,7 +383,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 5, "CH": 1, "CHO": 2, "CH2O": 2}, + unifac_result=[{"CH3": 5, "CH": 1, "CHO": 2, "CH2O": 2}, {'CH3': 5, 'CH2': 1, 'CH2O': 1, 'CHO': 3}], psrk_result={"CH3": 5, "CH": 1, "CHO": 2, "CH2O": 2}, joback_result={"-CH3": 5, "-CH2-": 2, ">CH-": 3, "-O- (non-ring)": 4}, ), @@ -393,7 +393,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 6, "CH": 2, "CH2O": 4, "CHO": 2}, + unifac_result=[{"CH3": 6, "CH": 2, "CH2O": 4, "CHO": 2}, {'CH3': 6, 'CH2': 1, 'CH': 1, 'CH2O': 3, 'CHO': 3}, {'CH3': 6, 'CH2': 2, 'CH2O': 2, 'CHO': 4}], psrk_result={"CH3": 6, "CH": 2, "CH2O": 4, "CHO": 2}, joback_result={"-CH3": 6, "-CH2-": 4, ">CH-": 4, "-O- (non-ring)": 6}, ), @@ -403,7 +403,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 6, "CHO": 3, "CH2O": 3, "CH": 1}, + unifac_result=[{"CH3": 6, "CHO": 3, "CH2O": 3, "CH": 1}, {'CH3': 6, 'CH2': 1, 'CH2O': 2, 'CHO': 4}], psrk_result={"CH3": 6, "CHO": 3, "CH2O": 3, "CH": 1}, joback_result={"-CH3": 6, "-CH2-": 3, ">CH-": 4, "-O- (non-ring)": 6}, ), @@ -413,7 +413,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3": 4, "CHO": 1, "CH2O": 1, "CH": 1}, + unifac_result=[{"CH3": 4, "CHO": 1, "CH2O": 1, "CH": 1}, {'CH3': 4, 'CH2': 1, 'CHO': 2}], psrk_result={"CH3": 4, "CHO": 1, "CH2O": 1, "CH": 1}, joback_result={"-CH3": 4, "-CH2-": 1, ">CH-": 2, "-O- (non-ring)": 2}, ), @@ -433,7 +433,7 @@ cases_module="ethers", r=None, q=None, - unifac_result={"CH3O": 2, "CH2": 1}, + unifac_result=[{"CH3O": 2, "CH2": 1}, {'CH3': 1, 'CH3O': 1, 'CH2O': 1}], psrk_result={"CH3O": 2, "CH2": 1}, joback_result={"-CH3": 2, "-CH2-": 1, "-O- (non-ring)": 2}, ), diff --git a/tests/cases/particulars.py b/tests/cases/particulars.py index 7a984cb..e9e8076 100644 --- a/tests/cases/particulars.py +++ b/tests/cases/particulars.py @@ -595,16 +595,6 @@ psrk_result={"CF4": 1}, joback_result={">C<": 1, "-F": 4}, ), - Case( - identifier="[O-][O+]=O", - identifier_type="smiles", - cases_module="particulars", - r=None, - q=None, - unifac_result={}, - psrk_result={"O3": 1}, - joback_result={"-O- (non-ring)": 1, "=O (other than above)": 2}, - ), Case( identifier="N(=O)Cl", identifier_type="smiles", diff --git a/tests/cases/tcase.py b/tests/cases/tcase.py index dce1060..14a24b8 100644 --- a/tests/cases/tcase.py +++ b/tests/cases/tcase.py @@ -4,6 +4,7 @@ from .alcohols import alcohols_cases from .aldehydes import aldehydes_cases from .aromatics import aromatics_cases +from .case import Case from .complex import complex_cases from .epoxides import epoxides_cases from .esters import esters_cases @@ -16,7 +17,6 @@ from .particulars import particulars_cases from .silicon import silicon_cases from .sulfur import sulfur_cases -from .case import Case import pytest @@ -29,72 +29,107 @@ # Class to define the test class for each model. class TCase(ABC): + """Class to define the test class for each model. + + This class is an abstract class that defines the structure of the test + class for each model. It defines the asserts method that should be + implemented in the subclasses, and the test methods for each type of + molecule. The test methods are parametrized with the solvers and the cases + of each type of molecule. + """ + @abstractmethod def asserts(self, case: Case, solver: ILPSolver) -> None: ... @pytest.mark.acids @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", acids_cases) + @pytest.mark.parametrize( + "case", acids_cases, ids=[c.identifier for c in acids_cases] + ) def test_acids(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.alcohols @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", alcohols_cases) + @pytest.mark.parametrize( + "case", alcohols_cases, ids=[c.identifier for c in alcohols_cases] + ) def test_alcohols(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.aldehydes @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", aldehydes_cases) + @pytest.mark.parametrize( + "case", aldehydes_cases, ids=[c.identifier for c in aldehydes_cases] + ) def test_aldehydes(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.aromatics @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", aromatics_cases) + @pytest.mark.parametrize( + "case", aromatics_cases, ids=[c.identifier for c in aromatics_cases] + ) def test_aromathics(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.complex @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", complex_cases) + @pytest.mark.parametrize( + "case", complex_cases, ids=[c.identifier for c in complex_cases] + ) def test_complex(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.epoxides @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", epoxides_cases) + @pytest.mark.parametrize( + "case", epoxides_cases, ids=[c.identifier for c in epoxides_cases] + ) def test_epoxides(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.esters @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", esters_cases) + @pytest.mark.parametrize( + "case", esters_cases, ids=[c.identifier for c in esters_cases] + ) def test_esters(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.ethers @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", ethers_cases) + @pytest.mark.parametrize( + "case", ethers_cases, ids=[c.identifier for c in ethers_cases] + ) def test_ethers(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.halogens @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", halogens_cases) + @pytest.mark.parametrize( + "case", halogens_cases, ids=[c.identifier for c in halogens_cases] + ) def test_halogens(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.hydrocarbons @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", hydrocarbons_cases) + @pytest.mark.parametrize( + "case", + hydrocarbons_cases, + ids=[c.identifier for c in hydrocarbons_cases], + ) def test_hydrocarbons(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.insaturated_hydrocarbons @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", insaturated_hydrocarbons_cases) + @pytest.mark.parametrize( + "case", + insaturated_hydrocarbons_cases, + ids=[c.identifier for c in insaturated_hydrocarbons_cases], + ) def test_insaturated_hydrocarbons( self, case: Case, solver: ILPSolver ) -> None: @@ -102,30 +137,42 @@ def test_insaturated_hydrocarbons( @pytest.mark.ketones @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", ketones_cases) + @pytest.mark.parametrize( + "case", ketones_cases, ids=[c.identifier for c in ketones_cases] + ) def test_ketones(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.nitrogen @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", nitrogen_cases) + @pytest.mark.parametrize( + "case", nitrogen_cases, ids=[c.identifier for c in nitrogen_cases] + ) def test_nitrogen(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.particulars @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", particulars_cases) + @pytest.mark.parametrize( + "case", + particulars_cases, + ids=[c.identifier for c in particulars_cases], + ) def test_particulars(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.silicon @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", silicon_cases) + @pytest.mark.parametrize( + "case", silicon_cases, ids=[c.identifier for c in silicon_cases] + ) def test_silicon(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) @pytest.mark.sulfur @pytest.mark.parametrize("solver", solvers) - @pytest.mark.parametrize("case", sulfur_cases) + @pytest.mark.parametrize( + "case", sulfur_cases, ids=[c.identifier for c in sulfur_cases] + ) def test_sulfur(self, case: Case, solver: ILPSolver) -> None: self.asserts(case, solver) diff --git a/tests/test_unifac.py b/tests/test_unifac.py index 88f0d00..fb08b4c 100644 --- a/tests/test_unifac.py +++ b/tests/test_unifac.py @@ -1,46 +1,50 @@ -from cases import ( - acids_cases, - alcohols_cases, - aldehydes_cases, - aromatics_cases, - Case, - TCase, - complex_cases, - epoxides_cases, - esters_cases, - ethers_cases, - halogens_cases, - hydrocarbons_cases, - insaturated_hydrocarbons_cases, - ketones_cases, - nitrogen_cases, - particulars_cases, - silicon_cases, - sulfur_cases, -) +from cases import TCase import pytest from ugropy import unifac -class TestUNIFAC: - @pytest.mark.parametrize("case", hydrocarbons_cases) - def test_unifac_hydrocarbons(self, case: Case): +@pytest.mark.unifac +class TestUNIFAC(TCase): + def asserts(self, case, solver): if case.unifac_result is None: pytest.skip( - f"No UNIFAC result for {case.identifier}, {case.cases_module}" + f"No UNIFAC result defined for {case.identifier}, " + f"{case.cases_module}" ) + + result = unifac.get_groups( + identifier=case.identifier, + identifier_type=case.identifier_type, + solver=solver, + search_multiple_solutions=True, + ) + + if len(result) > 1: + for r in result: + comp = r.subgroups in case.unifac_result + + if not comp: + message = ( + "\n" + f"Case: {case.identifier}\n" + f"Test module: {case.cases_module}\n" + f"Expected: {case.unifac_result}\n" + f"Obtained: {[r.subgroups for r in result]}" + ) + + assert False, message else: - result = unifac.get_groups( - case.identifier, - case.identifier_type, - search_multiple_solutions=True, - ) + comp = result[0].subgroups == case.unifac_result - if len(result) > 1: - assert ( - False - ), f"Fijate este tiene multiples: {case.identifier}, {case.cases_module}" + if not comp: + message = ( + "\n" + f"Case: {case.identifier}\n" + f"Test module: {case.cases_module}\n" + f"Expected: {case.unifac_result}\n" + f"Obtained: {result[0].subgroups}" + ) - same_groups = result[0].subgroups == case.unifac_result + assert False, message diff --git a/tests/test_unit_cases.py b/tests/test_unit_cases.py index e38ed8f..9c645fc 100644 --- a/tests/test_unit_cases.py +++ b/tests/test_unit_cases.py @@ -1,6 +1,6 @@ from cases import ( alcohols_cases, - aromathics_cases, + aromatics_cases, hydrocarbons_cases, insaturated_hydrocarbons_cases, ketones_cases, @@ -23,7 +23,7 @@ def test_no_duplicate_cases(): # Combine all the lists into a single list all_cases = ( - aromathics_cases + aromatics_cases + alcohols_cases + hydrocarbons_cases + insaturated_hydrocarbons_cases diff --git a/ugropy/core/frag_classes/base/fragmentation_model.py b/ugropy/core/frag_classes/base/fragmentation_model.py index 4254d85..8cf687e 100644 --- a/ugropy/core/frag_classes/base/fragmentation_model.py +++ b/ugropy/core/frag_classes/base/fragmentation_model.py @@ -107,6 +107,11 @@ def get_groups( problem.solve() + if not problem.selected_fragments: + # This could happend, no solution found. Example: + # "CC(C)(C)OC(=O)OC1=CC=CC=C1" on UNIFAC. + return self.set_fragmentation_result(mol, [{}]) + solutions = [] for selection in problem.selected_fragments: diff --git a/ugropy/groupscsv/unifac/unifac_subgroups.csv b/ugropy/groupscsv/unifac/unifac_subgroups.csv index a156aa6..cfe92ec 100644 --- a/ugropy/groupscsv/unifac/unifac_subgroups.csv +++ b/ugropy/groupscsv/unifac/unifac_subgroups.csv @@ -16,16 +16,16 @@ OH|[OH]|17.007 CH3OH|[CH3][OH]|32.042 H2O|[OH2]|18.015 ACOH|[cH0][OH]|29.018 -CH3CO|[CH3][C;!$(C-O)]=O|43.045 -CH2CO|[CH2][C;!$(C-O)]=O|42.037 -HCO|[CH;!$(C-O)]=O|29.018 +CH3CO|[CH3][CH0;!$(C-O)]=O|43.045 +CH2CO|[CH2][CH0;!$(C-O)]=O|42.037 +HCO|[CH;!$([CH]-O)]=O|29.018 CH3COO|[CH3][C](=O)[OH0]|59.044 CH2COO|[CH2][C](=O)[OH0]|58.036 HCOO|[CH](=O)[OH0]|45.017 CH3O|[CH3][OH0]|31.034 -CH2O|[CH2][OH0]|30.026 +CH2O|[CH2;!$([CH2]1[OH0][#6;r5;!c][#6;r5;!c][#6;r5;!c]1)][OH0]|30.026 CHO|[CH][OH0]|29.018 -THF|[CH2;R][O;R]|30.026 +THF|[CH2;r5;$([CH2]1[OH0][#6;r5;!c][#6;r5;!c][#6;r5;!c]1)][OH0;r5]|30.026 CH3NH2|[CH3][NH2]|31.058 CH2NH2|[CH2][NH2]|30.05 CHNH2|[CH][NH2]|29.042 @@ -75,7 +75,7 @@ HCON(CH2)2|[CH2]N([CH2])[CH]=O|71.078 CF3|[CH0](F)(F)(F)|69.005 CF2|[CH0](F)(F)|50.007 CF|[CH0](F)|31.009 -COO|[C](=O)[OH0]|44.009 +COO|[CH0](=O)[OH0]|44.009 SIH3|[SiX4H3]|31.1094 SIH2|[SiX4H2]|30.1016 SIH|[SiX4H]|29.0938