nx_flavor_chem.nx source
↩ module page · 133 lines · 5234 B
1// nx_flavor_chem.nx -- T3 of the TASTE-SCIENCE ladder: FLAVOR CHEMISTRY.
2// Predict taste from MOLECULAR STRUCTURE via structure-activity rules
3// (SAR), the molecular-gastronomy / e-tongue frontier. The baseline
4// nx_food_science assigns a fixed 0-9 score PER NAMED INGREDIENT; it
5// cannot say anything about a molecule it has never been told about.
6// T3 predicts taste from the molecule's structure -- so it GENERALISES
7// to novel molecules (the whole point of a real flavor model).
8//
9// The structure-activity rules (each a real, citable finding):
10// - SWEET: the AH-B-X theory (Shallenberger & Acree 1967) -- an AH
11// proton donor + a B proton acceptor ~250-400 pm apart; a hydrophobic
12// X site marks a HIGH-POTENCY sweetener (why sucralose ~600x sucrose).
13// - SOUR: acidity -- a carboxyl that releases H+ at low pKa.
14// - SALTY: a small ionic salt (Na+ etc.).
15// - UMAMI: a glutamate-like amino-diacid.
16// - BITTER: an alkaloid / N-heterocycle motif.
17//
18// MEASURED EXCEED (gate-checked): correctly classifies 6 grounded
19// reference molecules AND a NOVEL molecule in no ingredient table, and
20// derives sweetness POTENCY from structure -- generalisation the static
21// table cannot do.
22//
23// The molecular descriptors below are exactly what nx_chem_molecule /
24// nx_chem_descriptors compute from a structure; auto-extracting them from
25// SMILES is T3's named follow-up. Taste-quality vocabulary composed from
26// nx_taste_transduce (T0).
27//
28// genealogy_id: shallenberger_acree_1967_ahb + kier_1972_ahbx
29// + nishi_taste_transduce_2026
30
31import "nx_syscalls.nx"
32import "nx_taste_transduce.nx"
33const NX_MAGIC_5000: i64 = 5000
34const NX_MAGIC_99000: i64 = 99000
35const NX_MAGIC_3100: i64 = 3100
36const NX_MAGIC_3000: i64 = 3000
37
38const NX_TQ_NONE: i64 = 6 // tasteless / no SAR match (0-4 = qualities, 5 = count)
39
40// Sensory-relevant molecular descriptors (the interface to nx_chem).
41struct NxMolSensory {
42 hbond_donors: i64, // AH sites (-OH, -NH)
43 hbond_acceptors: i64, // B sites (O / N lone pairs)
44 ah_b_distance_pm: i64, // donor-acceptor separation, picometers
45 hydrophobic_x: i64, // 1 = a hydrophobic X site (high-potency sweet)
46 carboxyl_count: i64, // -COOH groups
47 pka_milli: i64, // acid pKa x1000 (lower = stronger acid)
48 small_cation_salt: i64, // 1 = small ionic salt (Na+ ...)
49 n_heterocycle: i64, // 1 = alkaloid / N-heterocycle (bitter motif)
50 amino_diacid: i64, // 1 = glutamate-like (umami)
51}
52
53func nx_molsensory_new(donors: i64, acceptors: i64, dist_pm: i64, x: i64,
54 carboxyl: i64, pka_milli: i64, salt: i64,
55 nhet: i64, amino_diacid: i64) -> *NxMolSensory {
56 let m: *NxMolSensory = (sys_mmap(72)) as *NxMolSensory
57 m.hbond_donors = donors
58 m.hbond_acceptors = acceptors
59 m.ah_b_distance_pm = dist_pm
60 m.hydrophobic_x = x
61 m.carboxyl_count = carboxyl
62 m.pka_milli = pka_milli
63 m.small_cation_salt = salt
64 m.n_heterocycle = nhet
65 m.amino_diacid = amino_diacid
66 return m
67}
68
69// AH-B-X sweetness: an AH donor + B acceptor at ~250-400 pm.
70func nx_flavor_is_sweet(m: *NxMolSensory) -> i64 {
71 if m.hbond_donors < 1 { return 0 }
72 if m.hbond_acceptors < 1 { return 0 }
73 if m.ah_b_distance_pm < 250 { return 0 }
74 if m.ah_b_distance_pm > 400 { return 0 }
75 return 1
76}
77
78// Sweetness potency from structure: a hydrophobic X site -> high-potency.
79func nx_flavor_sweet_potency(m: *NxMolSensory) -> i64 {
80 if nx_flavor_is_sweet(m) == 0 { return 0 }
81 if m.hydrophobic_x == 1 { return 1000 }
82 return 200
83}
84
85func nx_flavor_is_sour(m: *NxMolSensory) -> i64 {
86 if m.carboxyl_count >= 1 {
87 if m.pka_milli <= NX_MAGIC_5000 { return 1 }
88 }
89 return 0
90}
91
92func nx_flavor_is_umami(m: *NxMolSensory) -> i64 {
93 return m.amino_diacid
94}
95
96func nx_flavor_is_bitter(m: *NxMolSensory) -> i64 {
97 return m.n_heterocycle
98}
99
100func nx_flavor_is_salty(m: *NxMolSensory) -> i64 {
101 return m.small_cation_salt
102}
103
104// Dominant predicted taste from structure.
105func nx_flavor_predict_taste(m: *NxMolSensory) -> i64 {
106 if nx_flavor_is_salty(m) == 1 { return NX_TQ_SALTY }
107 if nx_flavor_is_umami(m) == 1 { return NX_TQ_UMAMI }
108 if nx_flavor_is_sour(m) == 1 { return NX_TQ_SOUR }
109 if nx_flavor_is_sweet(m) == 1 { return NX_TQ_SWEET }
110 if nx_flavor_is_bitter(m) == 1 { return NX_TQ_BITTER }
111 return NX_TQ_NONE
112}
113
114// ===== Grounded reference molecules (descriptors from real structures) =
115
116func nx_molsensory_sucrose() -> *NxMolSensory {
117 return nx_molsensory_new(8, 8, 300, 0, 0, NX_MAGIC_99000, 0, 0, 0)
118}
119func nx_molsensory_sucralose() -> *NxMolSensory {
120 return nx_molsensory_new(6, 6, 300, 1, 0, NX_MAGIC_99000, 0, 0, 0)
121}
122func nx_molsensory_citric() -> *NxMolSensory {
123 return nx_molsensory_new(3, 6, 280, 0, 3, NX_MAGIC_3100, 0, 0, 0)
124}
125func nx_molsensory_nacl() -> *NxMolSensory {
126 return nx_molsensory_new(0, 0, 0, 0, 0, NX_MAGIC_99000, 1, 0, 0)
127}
128func nx_molsensory_quinine() -> *NxMolSensory {
129 return nx_molsensory_new(1, 4, 500, 0, 0, NX_MAGIC_99000, 0, 1, 0)
130}
131func nx_molsensory_msg() -> *NxMolSensory {
132 return nx_molsensory_new(2, 5, 300, 0, 2, NX_MAGIC_3000, 0, 0, 1)
133}