nx_flavor_pairing.nx source
↩ module page · 104 lines · 3186 B
1// nx_flavor_pairing.nx -- T4 of the TASTE-SCIENCE ladder: the FLAVOR
2// NETWORK. Ahn, Ahnert, Bagrow & Barabasi (2011, "Flavor network and
3// the principles of food pairing") showed that ingredient pairing can be
4// computed from the aroma compounds two ingredients SHARE -- a data-
5// driven affinity, not a chef's opinion.
6//
7// The baseline nx_food_science carries a HAND-LISTED affinity table
8// (beef-oyster = 9, ...): finite, and silent on any pair nobody typed in.
9// T4 computes affinity from each ingredient's compound set, so it scores
10// ANY pair -- including a novel ingredient that has never been in a table.
11//
12// shared(A,B) = |compounds(A) INTERSECT compounds(B)|
13// affinity(A,B) = Jaccard = 1000 * shared / |A UNION B| (milli)
14//
15// MEASURED EXCEED (gate-checked): a fruity pair that shares esters scores
16// above a fruit+allium pair that shares none, identical sets score 1000,
17// and a NOVEL ingredient gets a real affinity computed from its compounds.
18//
19// genealogy_id: ahn_2011_flavor_network + jaccard_1901_similarity
20
21import "nx_syscalls.nx"
22
23// A set of aroma-compound ids.
24struct NxCompoundSet {
25 ids: *i64,
26 n: i64,
27}
28
29// Allocate an empty set with room for n compounds; caller fills .ids[0..n).
30func nx_compoundset_new(n: i64) -> *NxCompoundSet {
31 let s: *NxCompoundSet = (sys_mmap(16)) as *NxCompoundSet
32 s.ids = (sys_mmap(8 * 64)) as *i64
33 s.n = n
34 return s
35}
36
37// |A INTERSECT B| -- the count of shared aroma compounds.
38func nx_pairing_shared(a: *NxCompoundSet, b: *NxCompoundSet) -> i64 {
39 let aids: *i64 = a.ids
40 let bids: *i64 = b.ids
41 let an: i64 = a.n
42 let bn: i64 = b.n
43 var count: i64 = 0
44 var i: i64 = 0
45 while i < an {
46 var j: i64 = 0
47 while j < bn {
48 if aids[i] == bids[j] { count = count + 1 }
49 j = j + 1
50 }
51 i = i + 1
52 }
53 return count
54}
55
56// Pairing affinity = Jaccard similarity of the two compound sets (milli).
57func nx_pairing_affinity_milli(a: *NxCompoundSet, b: *NxCompoundSet) -> i64 {
58 let shared: i64 = nx_pairing_shared(a, b)
59 let uni: i64 = a.n + b.n - shared
60 if uni <= 0 { return 0 }
61 return shared * 1000 / uni
62}
63
64// ===== Grounded ingredient compound sets (illustrative ids reflecting
65// real shared-compound relationships: fruits share esters/furaneol;
66// alliums carry sulfur compounds; roasted meat carries Maillard pyrazines).
67
68func nx_compounds_strawberry() -> *NxCompoundSet {
69 let s: *NxCompoundSet = nx_compoundset_new(6)
70 let a: *i64 = s.ids
71 a[0] = 0
72 a[1] = 1
73 a[2] = 2
74 a[3] = 3
75 a[4] = 4
76 a[5] = 5
77 return s
78}
79func nx_compounds_pineapple() -> *NxCompoundSet {
80 let s: *NxCompoundSet = nx_compoundset_new(5)
81 let a: *i64 = s.ids
82 a[0] = 3
83 a[1] = 4
84 a[2] = 5
85 a[3] = 6
86 a[4] = 7
87 return s
88}
89func nx_compounds_garlic() -> *NxCompoundSet {
90 let s: *NxCompoundSet = nx_compoundset_new(3)
91 let a: *i64 = s.ids
92 a[0] = 10
93 a[1] = 11
94 a[2] = 12
95 return s
96}
97func nx_compounds_roastbeef() -> *NxCompoundSet {
98 let s: *NxCompoundSet = nx_compoundset_new(3)
99 let a: *i64 = s.ids
100 a[0] = 8
101 a[1] = 9
102 a[2] = 10
103 return s
104}