1
|
|
|
/* eslint-env node */ |
2
|
|
|
/*jslint node: true */ |
3
|
|
|
'use strict'; |
4
|
|
|
|
5
|
|
|
let jsonfile = require('jsonfile'); |
6
|
|
|
|
7
|
|
|
let resources = jsonfile.readFileSync('build/data/resources.json'); |
8
|
|
|
let elements = jsonfile.readFileSync('build/data/elements.json'); |
9
|
|
|
|
10
|
|
|
let isotopeMatrix = {}; |
11
|
|
|
|
12
|
|
|
function getAtomBreakdown(isotope) { |
13
|
|
|
let element = isotope.replace(/^[0-9]*/, ''); |
14
|
|
|
let number = parseInt(isotope.replace(element, ''), 10); |
15
|
|
|
let elementNumber = elements[element].number; |
16
|
|
|
let neutronNumber = number - elementNumber; |
17
|
|
|
return { |
18
|
|
|
atom: elementNumber, |
19
|
|
|
n: neutronNumber |
20
|
|
|
}; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
for (let element in elements) { |
24
|
|
|
for (let isotope in elements[element].isotopes) { |
25
|
|
|
let breakdown = getAtomBreakdown(isotope); |
26
|
|
|
if (!isotopeMatrix[breakdown.atom]) { |
27
|
|
|
isotopeMatrix[breakdown.atom] = {}; |
28
|
|
|
} |
29
|
|
|
isotopeMatrix[breakdown.atom][breakdown.n] = isotope; |
30
|
|
|
} |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
for (let element in elements) { |
34
|
|
|
for (let key in elements[element].isotopes) { |
35
|
|
|
let isotope = resources[key]; |
36
|
|
|
if (isotope.decay) { |
37
|
|
|
let ratioSum = 0; |
38
|
|
|
for (let decay in isotope.decay.decay_types) { |
39
|
|
|
ratioSum += isotope.decay.decay_types[decay].ratio; |
40
|
|
|
isotope.decay.decay_types[decay].reaction = generateReaction(key, decay); |
41
|
|
|
} |
42
|
|
|
let difference = 1 - ratioSum; |
43
|
|
|
if (Math.abs(difference) > 1e-7) { |
44
|
|
|
throw new Error('Ratios add up to '.concat(1 - difference, ' for ', key)); |
45
|
|
|
} |
46
|
|
|
} |
47
|
|
|
} |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
function generateReaction(isotope, type) { |
51
|
|
|
switch (type) { |
52
|
|
|
case 'beta-': |
53
|
|
|
return calculateReaction(isotope, 1, 'e-', 1, 0); |
54
|
|
|
case '2beta-': |
55
|
|
|
return calculateReaction(isotope, 2, 'e-', 2, 0); |
56
|
|
|
case 'beta+': |
57
|
|
|
return calculateReaction(isotope, 1, 'e+', -1, 0); |
58
|
|
|
case '2beta+': |
59
|
|
|
return calculateReaction(isotope, 2, 'e+', -2, 0); |
60
|
|
|
case 'electron_capture': |
61
|
|
|
return calculateReaction(isotope, 1, null, -1, 0); |
62
|
|
|
case 'alpha': |
63
|
|
|
return calculateReaction(isotope, 1, 'He2+', -2, -4); |
64
|
|
|
case 'beta+p': |
65
|
|
|
return calculateReaction(isotope, 1, 'e-', -2, -1); |
66
|
|
|
case 'beta-n': |
67
|
|
|
return calculateReaction(isotope, 1, 'e+', 1, -1); |
68
|
|
|
case 'SF': |
69
|
|
|
return calculateSF(isotope); |
70
|
|
|
default: |
71
|
|
|
throw new Error('Unrecognized decay type: ' + type); |
72
|
|
|
} |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
function calculateReaction(isotope, number, particle, protonDifference, isotopeDifference) { |
76
|
|
|
let element = isotope.replace(/^[0-9]*/, ''); |
77
|
|
|
let elementNumber = elements[element].number - 1; |
78
|
|
|
let listElements = Object.keys(elements); |
79
|
|
|
let otherElement = listElements[elementNumber + protonDifference]; |
80
|
|
|
|
81
|
|
|
let isotopeNumber = Number(isotope.replace(element, '')) + isotopeDifference; |
82
|
|
|
let product = isotopeNumber + otherElement; |
83
|
|
|
|
84
|
|
|
// We need all this convoluted logic to work around missing isotopes in the data set |
85
|
|
|
// essentially we look for the closest isotope to the target one, and consume/produce |
86
|
|
|
// free neutrons in the process |
87
|
|
|
let distance = 0; |
88
|
|
|
if (!resources[product]) { |
89
|
|
|
let candidate = null; |
90
|
|
|
// first we start looking for lighter isotopes |
91
|
|
|
for (let otherNumber = isotopeNumber; otherNumber > 0; otherNumber--) { |
92
|
|
|
let otherProduct = otherNumber + otherElement; |
93
|
|
|
if (resources[otherProduct]) { |
94
|
|
|
candidate = otherProduct; |
95
|
|
|
distance = isotopeNumber - otherNumber; |
96
|
|
|
break; |
97
|
|
|
} |
98
|
|
|
} |
99
|
|
|
// pay attention to the upper bound. 300 is bigger than any known isotope, so it is safe |
100
|
|
|
for (let otherNumber = isotopeNumber; otherNumber < 300; otherNumber++) { |
101
|
|
|
let otherProduct = otherNumber + otherElement; |
102
|
|
|
if (resources[otherProduct]) { |
103
|
|
|
// we only replace the candidate if the distance is smaller |
104
|
|
|
if (isotopeNumber - otherNumber < Math.abs(distance)) { |
105
|
|
|
candidate = otherProduct; |
106
|
|
|
distance = isotopeNumber - otherNumber; |
107
|
|
|
} |
108
|
|
|
break; |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
if (!candidate) { |
112
|
|
|
throw new Error('No candidate found for ' + isotope + ' replacing the missing isotope ' + product); |
113
|
|
|
} |
114
|
|
|
if(distance !== 0){ |
115
|
|
|
console.log('Isotope ',isotope,'decays to',candidate,'instead of',product,'with distance',distance); |
|
|
|
|
116
|
|
|
} |
117
|
|
|
product = candidate; |
118
|
|
|
} |
119
|
|
|
let energy = resources[isotope].energy - resources[product].energy; |
120
|
|
|
if (distance < 0) { |
121
|
|
|
energy -= resources.n.energy * distance; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
let reaction = {}; |
125
|
|
|
reaction.reactant = {}; |
126
|
|
|
reaction.reactant[isotope] = number; |
127
|
|
|
// if the isotope is heavier, the distance is negative and we produce neutrons |
128
|
|
|
if (distance < 0) { |
129
|
|
|
reaction.reactant.n = Math.abs(distance); |
130
|
|
|
} |
131
|
|
|
reaction.product = {}; |
132
|
|
|
reaction.product[product] = number; |
133
|
|
|
if (particle) { |
134
|
|
|
reaction.product[particle] = number; |
135
|
|
|
} |
136
|
|
|
// if the isotope is lighter, the distance is positive and we produce neutrons |
137
|
|
|
if (distance > 0) { |
138
|
|
|
reaction.product.n = distance; |
139
|
|
|
} |
140
|
|
|
reaction.product.eV = energy; |
141
|
|
|
|
142
|
|
|
return reaction; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
function calculateSF(isotope) { |
146
|
|
|
let breakdown = getAtomBreakdown(isotope); |
147
|
|
|
let halfNeutron = Math.floor(breakdown.n / 2); |
148
|
|
|
let halfAtom = Math.floor(breakdown.atom / 2); |
149
|
|
|
let firstHalf; |
150
|
|
|
for (let i = halfNeutron; i > 0; i--) { |
151
|
|
|
if (isotopeMatrix[halfAtom][i]) { |
152
|
|
|
firstHalf = isotopeMatrix[halfAtom][i]; |
153
|
|
|
break; |
154
|
|
|
} |
155
|
|
|
} |
156
|
|
|
let secondHalfNeutron = breakdown.n - halfNeutron; |
157
|
|
|
let secondHalfAtom = breakdown.atom - halfAtom; |
158
|
|
|
|
159
|
|
|
let secondHalf; |
160
|
|
|
for (let i = secondHalfNeutron; i > 0; i--) { |
161
|
|
|
if (isotopeMatrix[secondHalfAtom][i]) { |
162
|
|
|
secondHalf = isotopeMatrix[secondHalfAtom][i]; |
163
|
|
|
break; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
let firstHalfBreakdown = getAtomBreakdown(firstHalf); |
|
|
|
|
167
|
|
|
let secondHalfBreakdown = getAtomBreakdown(secondHalf); |
|
|
|
|
168
|
|
|
let neutronExcess = breakdown.n - firstHalfBreakdown.n - secondHalfBreakdown.n; |
169
|
|
|
|
170
|
|
|
let energy = resources[isotope].energy - resources[firstHalf].energy - |
171
|
|
|
resources[secondHalf].energy - resources.n.energy * neutronExcess; |
172
|
|
|
|
173
|
|
|
let reaction = {}; |
174
|
|
|
reaction.reactant = {}; |
175
|
|
|
reaction.reactant[isotope] = 1; |
176
|
|
|
reaction.product = {}; |
177
|
|
|
reaction.product[firstHalf] = 1; |
178
|
|
|
reaction.product[secondHalf] = reaction.product[secondHalf]+1 || 1; |
179
|
|
|
if (neutronExcess > 0) { |
180
|
|
|
reaction.product.n = neutronExcess; |
181
|
|
|
} |
182
|
|
|
if (energy > 0) { |
183
|
|
|
reaction.product.eV = energy; |
184
|
|
|
} |
185
|
|
|
return reaction; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
jsonfile.writeFileSync('build/data/resources.json', resources, { |
189
|
|
|
spaces: 2 |
190
|
|
|
}); |
191
|
|
|
|
192
|
|
|
jsonfile.writeFileSync('build/data/elements.json', elements, { |
193
|
|
|
spaces: 2 |
194
|
|
|
}); |
195
|
|
|
|