File size: 4,502 Bytes
f3ff0be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path

from pino.registry import AromaRegistry


PATH = Path(__file__).parents[1] / "scripts" / "build_genre_challenge_v2.py"
SPEC = spec_from_file_location("build_genre_challenge_v2", PATH)
MODULE = module_from_spec(SPEC)
assert SPEC.loader
SPEC.loader.exec_module(MODULE)


def record(genre, components):
    return {"genre": genre, "formula": components}


def component(cas, smiles=None, weight=0.5):
    if smiles is None:
        smiles = {"a": "CC", "b": "CCC", "c": "CCCC", "d": "CCCCC", "e": "CCCCCC", "f": "CCCCCCC", "g": "CCCCCCCC"}.get(cas, "CCO")
    return {"cas": cas, "smiles": smiles, "weight_fraction": weight}


def test_characterize_excludes_solvent_and_requires_valid_single_fragment_smiles():
    row = record("fougere", [component("64-17-5", weight=0.2), component("a"), component("b")])
    result = MODULE.characterize(row, 7)
    assert result["active_count"] == 2
    assert result["structurally_valid"] is True
    assert result["source_index"] == 7

    invalid = MODULE.characterize(record("fougere", [component("a", "not-smiles"), component("b")]), 8)
    assert invalid["structurally_valid"] is False


def test_composition_match_enforces_size_concentration_and_identity_calipers():
    base = MODULE.characterize(record("fougere", [component("a"), component("b")]), 0)
    matched = MODULE.characterize(record("floral_woody", [component("c"), component("d")]), 1)
    overlap = MODULE.characterize(record("floral_woody", [component("a"), component("d")]), 2)
    singleton = MODULE.characterize(record("floral_woody", [component("c", weight=1.0)]), 3)
    assert MODULE.composition_match(base, matched)
    assert not MODULE.composition_match(base, overlap)
    assert not MODULE.composition_match(base, singleton)


def test_census_keeps_wildcard_out_of_primary_pool():
    rows = [
        record("fougere", [component("a"), component("b")]),
        record("floral_woody", [component("c"), component("d")]),
        record("wildcard", [component("e"), component("f")]),
        record("wildcard", [component("g", weight=1.0)]),
    ]
    result = MODULE.census(rows)
    assert result["per_substantive_genre"]["fougere"]["composition_matchable"] == 1
    assert result["wildcard_open_set_pool"] == {
        "multi_component_structurally_valid": 1,
        "singleton_sanity_check": 1,
    }


def test_characterize_resolves_cas_smiles_keys_expands_naturals_and_aggregates(tmp_path):
    registry = AromaRegistry(tmp_path / "registry.db")
    registry.add({"cas": "5989-27-5", "name": "limonene", "smiles": "CC1=CCC(CC1)C(=C)C"})
    registry.add({"cas": "123-35-3", "name": "myrcene", "smiles": "CC(=CCCC(=C)C=C)C"})
    row = record("citrus_cologne", [
        component("NATURAL:8008-57-9", smiles="", weight=0.5),
        component("SMILES:CCO", smiles="", weight=0.25),
        component("5989-27-5", smiles="", weight=0.25),
    ])
    result = MODULE.characterize(row, 0, registry)
    registry.close()
    assert result["structurally_valid"] is True
    assert result["active_count"] == 3
    assert result["unresolved_identifiers"] == []
    assert len(result["ingredients"]) == 3


def test_characterize_reports_unresolved_identifier(tmp_path):
    with AromaRegistry(tmp_path / "registry.db") as registry:
        result = MODULE.characterize(record("fougere", [component("unknown", smiles="")]), 0, registry)
    assert result["structurally_valid"] is False
    assert result["unresolved_identifiers"] == ["unknown"]


def test_duplicate_collapse_is_order_independent_and_conservative():
    rows = []
    for index, weights in enumerate(((.5, .5), (.51, .49), (.8, .2))):
        result = MODULE.characterize(record("fougere", [
            component("a", weight=weights[0]), component("b", weight=weights[1])]), index)
        result["resolved_components"], _ = MODULE.resolved_active_components(record("fougere", [
            component("b", weight=weights[1]), component("a", weight=weights[0])]))
        rows.append(result)
    kept, audit = MODULE.collapse_duplicates(rows)
    assert [row["source_index"] for row in kept] == [0, 2]
    assert audit["collapsed_records"] == 1


def test_natural_weight_share_uses_pre_expansion_formula_weight():
    row = record("citrus_cologne", [
        component("NATURAL:8008-57-9", smiles="", weight=.25),
        component("SMILES:CCO", smiles="", weight=.75),
    ])
    assert MODULE.natural_weight_share(row) == .25