nx_peptide_chain.nx source
↩ module page · 147 lines · 5660 B
1// nx_peptide_chain.nx -- CHEMISTRY SUITE / MULTI-CHAIN PEPTIDE rung. Closes
2// the last gap the peptide lane declared: an assembly of SEVERAL chains held
3// together by disulfide bridges. Insulin is the case that matters -- two
4// chains, three bridges, and one of the most counterfeited biologics there
5// is -- and nothing before this could weigh it.
6//
7// WHY IT IS NOT JUST "ADD THE CHAINS UP". Two things break that:
8// 1. Every chain carries its OWN terminal water, so an N-chain assembly
9// has N waters, not one. Treating insulin as a single 51-residue
10// sequence is short by a whole water (18.011 Da).
11// 2. Bridges are owned by the ASSEMBLY, not by any chain. An inter-chain
12// disulfide belongs to neither A nor B. So pca_add_chain REFUSES a
13// builder that already carries its own bridges -- otherwise the same
14// -2.016 Da gets subtracted twice and the answer looks plausible.
15//
16// INTRA AND INTER ARE TRACKED SEPARATELY, because they mean different
17// things. Both cost the same mass, but only INTER-chain bridges hold the
18// assembly together: linking N chains needs at least N-1 of them.
19// pca_is_covalently_linked enforces exactly that, so a "two-chain" molecule
20// with only intra-chain bridges is correctly reported as two separate
21// peptides that happen to be in the same tube.
22//
23// THE CYSTEINE BUDGET IS A REAL CONSTRAINT, NOT A FORMALITY. Each bridge
24// consumes two cysteines, and a claim of more bridges than the sequences
25// can supply produces a mass that looks entirely reasonable. Insulin is
26// the perfect witness: 6 cysteines, 3 bridges, ZERO left over.
27//
28// Grounding (cited; researcher-groundable):
29// published_monoisotopic_mass_human_insulin_5803_6375 (the anchor)
30// human_insulin_a_b_chain_sequences_and_disulfide_map
31// disulfide_bond_two_hydrogen_loss (via nx_peptide_ext)
32//
33// genealogy_id: peptide_chemistry + nishi_chem_suite
34
35import "nx_syscalls.nx"
36import "nx_peptide.nx"
37import "nx_peptide_ext.nx"
38
39// ===== The assembly ===================================================
40
41struct NxPepAssembly {
42 mass_sum_q4: i64, // sum of finished chain masses, each with its water
43 n_chains: i64,
44 n_cys: i64,
45 n_ss_intra: i64,
46 n_ss_inter: i64,
47 valid: i64,
48}
49
50func nx_pep_assembly_new() -> *NxPepAssembly {
51 let a: *NxPepAssembly = (sys_mmap(64)) as *NxPepAssembly
52 a.mass_sum_q4 = 0
53 a.n_chains = 0
54 a.n_cys = 0
55 a.n_ss_intra = 0
56 a.n_ss_inter = 0
57 a.valid = 1
58 return a
59}
60
61// Cysteines in a standard sequence -- the bridge budget's supply side.
62func pca_count_cys(seq: *u8) -> i64 {
63 return pep_count_aa(seq, PEP_C)
64}
65
66// Add a finished chain. REFUSES a builder that carries its own bridges:
67// those belong to the assembly, and accepting them here would subtract the
68// same mass twice.
69func pca_add_chain(a: *NxPepAssembly, b: *NxPepBuild, n_cys: i64) -> i64 {
70 if b.n_ss != 0 { a.valid = 0; return 0 }
71 let m: i64 = pex_mass_q4(b)
72 if m == PEP_INVALID { a.valid = 0; return 0 }
73 a.mass_sum_q4 = a.mass_sum_q4 + m
74 a.n_chains = a.n_chains + 1
75 a.n_cys = a.n_cys + n_cys
76 return a.valid
77}
78
79// Convenience for a plain standard-residue chain: builds it, counts its own
80// cysteines, and adds it.
81func pca_add_chain_seq(a: *NxPepAssembly, seq: *u8) -> i64 {
82 let b: *NxPepBuild = nx_pep_build_new()
83 pex_add_seq(b, seq)
84 let nc: i64 = pca_count_cys(seq)
85 return pca_add_chain(a, b, nc)
86}
87
88func pca_add_intra_disulfide(a: *NxPepAssembly) -> i64 { a.n_ss_intra = a.n_ss_intra + 1; return 0 }
89func pca_add_inter_disulfide(a: *NxPepAssembly) -> i64 { a.n_ss_inter = a.n_ss_inter + 1; return 0 }
90
91func pca_total_ss(a: *NxPepAssembly) -> i64 {
92 return a.n_ss_intra + a.n_ss_inter
93}
94
95// ===== Structural validity ============================================
96
97// Cysteines left unpaired. NEGATIVE means the assembly claims more bridges
98// than its sequences can supply -- chemically impossible.
99func pca_free_cys(a: *NxPepAssembly) -> i64 {
100 let used: i64 = pca_total_ss(a) * 2
101 return a.n_cys - used
102}
103
104func pca_bridges_possible(a: *NxPepAssembly) -> i64 {
105 let free: i64 = pca_free_cys(a)
106 if free < 0 { return 0 }
107 return 1
108}
109
110// N chains need at least N-1 INTER-chain bridges to be one molecule.
111func pca_is_covalently_linked(a: *NxPepAssembly) -> i64 {
112 if a.n_chains <= 0 { return 0 }
113 if a.n_chains == 1 { return 1 }
114 let need: i64 = a.n_chains - 1
115 if a.n_ss_inter >= need { return 1 }
116 return 0
117}
118
119// ===== Mass ===========================================================
120//
121// Sum of the chains (each already carrying its own water and terminal
122// modifications), less two hydrogens per bridge. REFUSES if the cysteine
123// budget is exceeded, because that mass would look perfectly reasonable.
124
125func pca_mass_q4(a: *NxPepAssembly) -> i64 {
126 if a.valid != 1 { return PEP_INVALID }
127 if a.n_chains <= 0 { return PEP_INVALID }
128 let ok: i64 = pca_bridges_possible(a)
129 if ok != 1 { return PEP_INVALID }
130 let ss: i64 = pca_total_ss(a) * PEX_SS_DELTA_Q4
131 return a.mass_sum_q4 + ss
132}
133
134// Reduced mass: every bridge broken, chains still summed. What a reducing
135// agent gives you, and the difference from pca_mass_q4 is a direct readout
136// of how many bridges were really there.
137func pca_mass_reduced_q4(a: *NxPepAssembly) -> i64 {
138 if a.valid != 1 { return PEP_INVALID }
139 if a.n_chains <= 0 { return PEP_INVALID }
140 return a.mass_sum_q4
141}
142
143func pca_mz_q4(a: *NxPepAssembly, z: i64) -> i64 {
144 let m: i64 = pca_mass_q4(a)
145 if m == PEP_INVALID { return PEP_INVALID }
146 return pep_mz_q4(m, z)
147}