Skip to content

Commit db8361f

Browse files
committed
feat(core): Add an about command
Although it is not really satisfying.
1 parent 01db17a commit db8361f

File tree

2 files changed

+142
-40
lines changed

2 files changed

+142
-40
lines changed

packages/core/__tests__/core.js

+22
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,26 @@ describe('@ector/core', () => {
198198
expect(ECTOR.getResponse(ector)).toBe('');
199199
});
200200
});
201+
202+
describe('about', () => {
203+
/** @type ECTOR.ECTOR */
204+
let ector;
205+
beforeEach(() => {
206+
ector = ECTOR.addEntry({}, "i don't know what to add");
207+
ector = ECTOR.addEntry(ector, 'this is more of an experiment');
208+
ector = ECTOR.addEntry(ector, 'let us see what it yields');
209+
});
210+
211+
it('should activate entry nodes in @about cns', () => {
212+
ector = ECTOR.about(ector, 'see');
213+
expect(ector.cns['@about']).toBeDefined();
214+
expect(ector.cns['@about']['wsee'].value).toBeGreaterThan(0);
215+
});
216+
217+
it('should return most activated nodes', () => {
218+
ector = ECTOR.about(ector, 'experiment');
219+
expect(Object.keys(ector.cns['@about'])).toHaveLength(1);
220+
expect(Object.keys(ector.cns['@about'])).toStrictEqual(['wexperiment']);
221+
});
222+
});
201223
});

packages/core/src/core.js

+120-40
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,24 @@
22

33
import rwc from 'random-weighted-choice';
44
import Tokenizer from 'sentence-tokenizer';
5-
import { ConceptNetwork, addLink, addNode, incrementBeginning, incrementEnd, incrementMiddle, getLinksFrom, getLinksTo, getNodeIndex } from '@ector/concept-network';
6-
import { ConceptNetworkState, activate, propagate, getActivationValue } from '@ector/state';
5+
import {
6+
ConceptNetwork,
7+
addLink,
8+
addNode,
9+
incrementBeginning,
10+
incrementEnd,
11+
incrementMiddle,
12+
getLinksFrom,
13+
getLinksTo,
14+
getNodeIndex,
15+
} from '@ector/concept-network';
16+
import {
17+
ConceptNetworkState,
18+
activate,
19+
propagate,
20+
getActivationValue,
21+
getActivatedTypedNodes,
22+
} from '@ector/state';
723

824
/**
925
* @typedef {Object<string, any>} ECTOR
@@ -77,7 +93,10 @@ export function addEntry(ector, entry) {
7793
cn = addLink(cn, prevTokenLabel, tokenLabel);
7894
}
7995
prevTokenLabel = tokenLabel;
80-
return { tokenLabels: [...tokenLabels, tokenLabel], prevTokenLabel };
96+
return {
97+
tokenLabels: [...tokenLabels, tokenLabel],
98+
prevTokenLabel,
99+
};
81100
},
82101
{ tokenLabels: [], prevTokenLabel: null },
83102
);
@@ -115,23 +134,29 @@ function choseToken(state, temperature) {
115134
const tokens = Object.keys(state)
116135
.filter(label => label.startsWith('w'))
117136
.filter(label => state[label].value >= maxActivationValue - 10);
118-
const toChoose = tokens.map(label => ({ weight: state[label].value, id: label }));
137+
const toChoose = tokens.map(label => ({
138+
weight: state[label].value,
139+
id: label,
140+
}));
119141
const chosenNode = rwc(toChoose, temperature);
120142
return chosenNode;
121143
}
122144

123145
/**
124-
* Generate the end of a sentence, adding tokens to the list of token
125-
* nodes in phrase.
126-
*
127-
* @param {ConceptNetwork} cn Network of tokens
128-
* @param {ConceptNetworkState} cns State of the network (activation values)
129-
* @param {{ id: string, weight: number }[]} phraseNodes array of token nodes
130-
* @param {number} temperature
131-
* @returns {{ id: string, weight: number }[]} array of token nodes (end of phrase)
132-
**/
146+
* Generate the end of a sentence, adding tokens to the list of token
147+
* nodes in phrase.
148+
*
149+
* @param {ConceptNetwork} cn Network of tokens
150+
* @param {ConceptNetworkState} cns State of the network (activation values)
151+
* @param {{ id: string, weight: number }[]} phraseNodes array of token nodes
152+
* @param {number} temperature
153+
* @returns {{ id: string, weight: number }[]} array of token nodes (end of phrase)
154+
**/
133155
function generateForwards(cn, cns, phraseNodes, temperature) {
134-
const outgoingLinks = getLinksFrom(cn, phraseNodes[phraseNodes.length -1].id)
156+
const outgoingLinks = getLinksFrom(
157+
cn,
158+
phraseNodes[phraseNodes.length - 1].id,
159+
);
135160

136161
/**
137162
* @ignore
@@ -140,24 +165,31 @@ function generateForwards(cn, cns, phraseNodes, temperature) {
140165
const toNode = cn.node[link.to];
141166
// When toNode is a word token
142167
if (toNode.label.startsWith('w')) {
143-
const activationValue = Math.max(getActivationValue(cns, toNode.label), 1);
144-
const repeatNb = phraseNodes.filter( ({ id }) => id === toNode.label).length;
168+
const activationValue = Math.max(
169+
getActivationValue(cns, toNode.label),
170+
1,
171+
);
172+
const repeatNb = phraseNodes.filter(({ id }) => id === toNode.label)
173+
.length;
145174
const len = toNode.label.length;
146175
// If the node is not present more than ~3 times
147176
if (repeatNb * len <= 5 * 3) {
148177
const repetition = 1 + repeatNb * repeatNb * len;
149-
return [...nodes, {
150-
id: toNode.label,
151-
weight: link.coOcc * activationValue / repetition
152-
}];
178+
return [
179+
...nodes,
180+
{
181+
id: toNode.label,
182+
weight: (link.coOcc * activationValue) / repetition,
183+
},
184+
];
153185
}
154186
}
155187
return [...nodes];
156-
}, [])
188+
}, []);
157189

158190
// Stop condition
159191
if (nextNodes.length === 0) {
160-
return phraseNodes;
192+
return phraseNodes;
161193
}
162194
// Choose one node among the tokens following the one at the end of the
163195
// phrase
@@ -166,46 +198,59 @@ function generateForwards(cn, cns, phraseNodes, temperature) {
166198
const chosenTokenNode = { id: cn.node[chosenItemIndex].label, weight: -1 };
167199

168200
// Recursively generate the remaining of the phrase
169-
return generateForwards(cn, cns, [...phraseNodes, chosenTokenNode], temperature);
201+
return generateForwards(
202+
cn,
203+
cns,
204+
[...phraseNodes, chosenTokenNode],
205+
temperature,
206+
);
170207
}
171208

172209
/**
173210
* Generate the begining of a sentence, adding tokens to the list of token
174211
* nodes in phrase.
175212
*
176-
* @param {ConceptNetwork} cn Network of tokens
177-
* @param {ConceptNetworkState} cns State of the network (activation values)
178-
* @param {{ id: string, weight: number }[]} phraseNodes array of token nodes
179-
* @param {number} temperature
180-
* @returns {{ id: string, weight: number }[]} array of token nodes (end of phrase)
213+
* @param {ConceptNetwork} cn Network of tokens
214+
* @param {ConceptNetworkState} cns State of the network (activation values)
215+
* @param {{ id: string, weight: number }[]} phraseNodes array of token nodes
216+
* @param {number} temperature
217+
* @returns {{ id: string, weight: number }[]} array of token nodes (end of phrase)
181218
**/
182219
function generateBackwards(cn, cns, phraseNodes, temperature) {
183-
const incomingLinks = getLinksTo(cn, phraseNodes[0].id)
220+
const incomingLinks = getLinksTo(cn, phraseNodes[0].id);
184221
/**
185222
* @ignore
186223
* @type Array<{ id: string, weight: number }> */
187224
const previousNodes = incomingLinks.reduce((nodes, link) => {
188225
const fromNode = cn.node[link.from];
189226
// When fromNode is a word token
190227
if (fromNode.label.startsWith('w')) {
191-
const activationValue = Math.max(getActivationValue(cns, fromNode.label), 1);
192-
const repeatNb = phraseNodes.filter( ({ id }) => id === fromNode.label).length;
228+
const activationValue = Math.max(
229+
getActivationValue(cns, fromNode.label),
230+
1,
231+
);
232+
const repeatNb = phraseNodes.filter(
233+
({ id }) => id === fromNode.label,
234+
).length;
193235
const len = fromNode.label.length;
194236
// If the node is not present more than ~3 times
195237
if (repeatNb * len <= 5 * 3) {
196238
const repetition = 1 + repeatNb * repeatNb * len;
197-
return [...nodes, {
198-
id: fromNode.label,
199-
weight: link.coOcc * activationValue / repetition
200-
}];
239+
return [
240+
...nodes,
241+
{
242+
id: fromNode.label,
243+
weight: (link.coOcc * activationValue) / repetition,
244+
},
245+
];
201246
}
202247
}
203248
return [...nodes];
204249
}, []);
205250

206251
// Stop condition
207252
if (previousNodes.length === 0) {
208-
return phraseNodes;
253+
return phraseNodes;
209254
}
210255
// Choose one node among the tokens following the one at the end of the
211256
// phrase
@@ -214,7 +259,12 @@ function generateBackwards(cn, cns, phraseNodes, temperature) {
214259
const chosenTokenNode = { id: cn.node[chosenItemIndex].label, weight: -1 };
215260

216261
// Recursively generate the remaining of the phrase
217-
return generateBackwards(cn, cns, [chosenTokenNode, ...phraseNodes], temperature);
262+
return generateBackwards(
263+
cn,
264+
cns,
265+
[chosenTokenNode, ...phraseNodes],
266+
temperature,
267+
);
218268
}
219269

220270
/**
@@ -248,7 +298,7 @@ export function generateResponse(ector) {
248298
...ector,
249299
cns,
250300
response,
251-
responseLabels: responseItems.map(({ id }) => id)
301+
responseLabels: responseItems.map(({ id }) => id),
252302
});
253303
}
254304

@@ -266,8 +316,8 @@ export function linkNodesToLastSentence(ector, nodeLabels = []) {
266316
const cn = nodeLabels.reduce((cn, nodeLabel) => {
267317
// QUESTION: is it the right direction(?)
268318
return addLink(cn, nodeLabel, ector.lastSentenceLabel);
269-
}, ector.cn)
270-
return Object.freeze({ ...ector, cn })
319+
}, ector.cn);
320+
return Object.freeze({ ...ector, cn });
271321
}
272322

273323
/**
@@ -279,3 +329,33 @@ export function linkNodesToLastSentence(ector, nodeLabels = []) {
279329
export function getResponse(ector) {
280330
return ector.response || '';
281331
}
332+
333+
/**
334+
* Activates the nodes of the entry in @about state and returns ECTOR and the
335+
* array of activated nodes.
336+
*
337+
* @param {ECTOR} ector
338+
* @param {string} entry
339+
* returns {ECTOR}
340+
*/
341+
export function about(ector, entry) {
342+
const tokenizer = new Tokenizer('@about', 'ECTOR');
343+
tokenizer.setEntry(entry);
344+
const sentences = tokenizer.getSentences();
345+
const tokens = tokenizer.getTokens(0);
346+
347+
let state = sentences.reduce((cns, sentence) => {
348+
return activate(cns, `s${sentence}`);
349+
}, {});
350+
state = tokens.reduce((cns, token) => {
351+
return activate(cns, `w${token}`);
352+
}, state);
353+
state = propagate(ector.cn, state);
354+
state = propagate(ector.cn, state);
355+
const newEctor = {
356+
...ector,
357+
username: '@about',
358+
cns: { ...ector.cns, '@about': state },
359+
};
360+
return newEctor;
361+
}

0 commit comments

Comments
 (0)