Explorar el Código

U Neuron (input order)

Piotr Labudda hace 6 años
padre
commit
b3a68bf972

+ 2 - 1
SE/se-lib/Route/Test/Neuron.php

@@ -17,7 +17,8 @@ class Route_Test_Neuron extends RouteBase {// TODO: UrlActionBase @see Route_Url
         UI::inlineJS(__FILE__ . '.NeuronView.js', [
             'MAKE_STORE_FUNCTION_NAME' => "makeNeuronStore",
             'HTML_ID' => "p5widget-test-neuron",
-            'INITIAL_DATA' => "AABBBBAABBAABB",
+            // 'INITIAL_DATA' => "AABBBBAABBAABB",
+            'INITIAL_DATA' => "BCAAAAABCABCABCABCABCABCABCABC",
             // 'INITIAL_DATA' => implode("\n", [
             //     "A",
             //     "AAA",

+ 45 - 10
SE/se-lib/Route/Test/Neuron.php.NeuronView.js

@@ -1,3 +1,4 @@
+var React = window.p5VendorJs.React;
 var createReactClass = window.p5VendorJs.createReactClass;
 var h = window.p5VendorJs.React.createElement;
 var ReactDOM = window.p5VendorJs.ReactDOM;
@@ -26,7 +27,7 @@ var DEFAULT_CONFIG = {
 	ui_output_height: 300,
 	ui_max_receptor_r: 12,
 	ui_space_y: 30,
-	config_anim_speed: 500,
+	config_anim_speed: 200,
 	config_read_speed_multiplier: 4,
 	config_charge_receptor_at_input: 1,
 	config_max_receptor_charge: 1,
@@ -35,15 +36,15 @@ var DEFAULT_CONFIG = {
 	config_discharge_max_in_new_neuron_from_one: 1, // 0.7
 	config_strategy_overcharge: "LEAVE_ALMOST_MAX", // "REMOVE_CHARGE" | "LEAVE_HALF_CHARGE" | "LEAVE_ALMOST_MAX"
 }
-var DEFAULT_NEURON = {
-	value: '',
-	charge: 0,
-	maxCharge: DEFAULT_CONFIG.config_max_neuron_charge,
-	uiShape: "circle",
-	ui: { cx: 0, cy: 0 },
-	source: [],
-	next: [],
-}
+// var DEFAULT_NEURON = {
+//     value: '',
+//     charge: 0,
+//     maxCharge: DEFAULT_CONFIG.config_max_neuron_charge,
+//     uiShape: "circle",
+//     ui: { cx: 0, cy: 0 },
+//     source: [],
+//     next: [],
+// }
 
 function LOG__render(listLogEntries) {
 	// console.log("DBG", listLogEntries)
@@ -144,6 +145,7 @@ var NeuronView = createReactClass({
 	handleReset: function (event) {
 		event.preventDefault();
 		DBG && console.log('DBG:NeuronView:handleReset...');
+		this._log = []
 		this.props.store.dispatch({ type: 'INIT', inputText: this.state.inputText, config: this.state })
 	},
 	handlePause: function (event) {
@@ -262,6 +264,7 @@ var NeuronView = createReactClass({
 			case 'receptor': return this.renderChargeOnReceptor(charge.nodeIdx, charge.charge, idx);
 			case 'neuron': return this.renderChargeOnNeuron(charge.nodeIdx, charge.charge, idx);
 			case 'connection': return this.renderChargeOnConnection(charge.nodeIdx, charge.charge, idx);
+			case 'dbl_conn': return this.renderChargeOnDoubleConnection(charge.nodeIdx, charge.charge, idx);
 			default: {
 				DBG && console.warn("Not implemented render charge type '" + charge.nodeType + "'", { charge, idx })
 				return null;
@@ -335,6 +338,38 @@ var NeuronView = createReactClass({
 			}))
 		]);
 	},
+	renderChargeOnDoubleConnection: function (idx, charge, chargeIdx) {
+		var connNode = this.props.store.getDoubleConnection(idx)
+		var chargeNode = this.props.store.getCharge(chargeIdx)
+		var connFromNode = this.props.store.getConnection(connNode.connIdx[0])
+		var connToNode = this.props.store.getConnection(connNode.connIdx[1])
+		DBG1 && console.warn("TODO:NeuronView:renderChargeOnDoubleConnection", { idx, charge, chargeIdx, connNode, chargeNode })
+
+		return h(React.Fragment, {}, [
+			this.renderChargeOnConnection(connNode.connIdx[0], chargeNode.fromCharge, '' + chargeIdx + '-from'),
+			this.renderChargeOnConnection(connNode.connIdx[1], chargeNode.toCharge, '' + chargeIdx + '-to'),
+		]);
+		// if (!sourceNode) {
+		//     DBG && console.warn("Missing source node at renderChargeOnConnection", { charge, idx })
+		//     return null;
+		// }
+		// var uiConnPos = this.getConnectionUIPos(sourceNode) // @return { x1, y1, x2, y2 }
+		// var uiPos = {
+		//     cx: Math.min(uiConnPos.x1, uiConnPos.x2) + Math.abs((uiConnPos.x1 - uiConnPos.x2) / 2),
+		//     cy: Math.min(uiConnPos.y1, uiConnPos.y2) + Math.abs((uiConnPos.y1 - uiConnPos.y2) / 2),
+		// }
+		// DBG && console.log("DBG:NeuronView:renderChargeOnConnection", { uiConnPos, uiPos })
+		// // sourceNode: { fromType: 'receptor', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 }
+
+		// return h('g', { p5_node_id: "charge-idx-" + chargeIdx, p5_node_type: "charge-on-conn" }, [
+		//     h('circle', Object.assign({}, uiPos, {
+		//         r: 5,
+		//         stroke: "#00d0ff",
+		//         fill: "#fbfbfb",
+		//         strokeWidth: "3px",
+		//     }))
+		// ]);
+	},
 	renderNeuron: function (neuron, idx) {
 		var chargeValue = this.props.store.getNeuronCharge(idx)
 		// var chargePr = (100 * chargeValue) / this.state.config_max_neuron_charge;

+ 171 - 120
SE/se-lib/Route/Test/Neuron.php.makeNeuronStore.js

@@ -2,28 +2,10 @@ var MAKE_STORE_FUNCTION_NAME = MAKE_STORE_FUNCTION_NAME || 'makeNeuronStore'
 var DBG = DBG || false;
 var DBG1 = true;
 
-// var STRATEGY_OVERCHARGE = [
-//     "REMOVE_CHARGE",
-//     "LEAVE_HALF_CHARGE",
-//     "LEAVE_ALMOST_MAX",
-// ]
-
-var DEFAULT_CONFIG = {
-	ui_output_width: 800,
-	ui_max_receptor_r: 12,
-	ui_space_y: 50,
-	config_anim_speed: 300,
-	config_read_speed_multiplier: 4,
-	config_charge_receptor_at_input: 1,
-	config_max_receptor_charge: 1,
-	config_max_neuron_charge: 1.5,
-	config_discharge_per_tick: 0.1,
-	config_discharge_max_in_new_neuron_from_one: 0.7,
-}
 var DEFAULT_NEURON = {
 	value: '',
 	charge: 0,
-	maxCharge: DEFAULT_CONFIG.config_max_neuron_charge,
+	maxCharge: 1.5, // DEFAULT_CONFIG.config_max_neuron_charge,
 	uiShape: "circle",
 	ui: { cx: 0, cy: 0 },
 	source: [],
@@ -53,6 +35,23 @@ function makeNeuronStore() {
 	var _animIntervalID = null
 	var _callback = null
 
+	function stateReset(inputText, config) {
+		_inputText = inputText
+		_config = config
+		_state = makeDefaultNeuronsStoreState()
+
+		var distinct = function (value, idx, self) {
+			return (idx === self.indexOf(value));
+		};
+		var foundLetters = inputText.split("").filter(distinct);
+		_state.receptor = foundLetters.sort().map(makeReceptor);
+		_state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; });
+
+		if (_animIntervalID) clearInterval(_animIntervalID)
+
+		_notifySubscribers();
+	}
+
 	function readCharFromInput() {
 		if (_state.inputReadPos >= _inputText.length) return null;
 
@@ -105,8 +104,12 @@ function makeNeuronStore() {
 
 
 		// 2. check overcharged nodes
-		// 2.1 check overcharged Receptor
-		// 2.2 check overcharged Neuron
+		// 2.1 check overcharged Neuron
+		// 2.1.1 is overcharged Neuron
+		// 2.1.1.1 is another Neuron with Charge
+		// 2.1.1.2 ! another Neuron with Charge
+		// 2.1.2 ! overcharged Neuron
+		// 2.2 check overcharged Receptor
 		{ // check overcharged nodes
 			var overchargedNeuronIdx = _state.neuron.reduce(function (ret, neuron, idx) {
 				var charge = getNeuronCharge(idx)
@@ -134,7 +137,7 @@ function makeNeuronStore() {
 					if (null === ret) return { idx: idx, charge: charge }
 					return (charge > ret.charge) ? { idx: idx, charge: charge } : ret;
 				}, null)
-				DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.1", { overcharged, firstNeuron, secondNeuronWithCharge })
+				DBG1 && console.log("TODO:NeuronStore:forwardAnim:2.1(check overcharged)/neuron.1", { overcharged, firstNeuron, secondNeuronWithCharge })
 				if (null !== secondNeuronWithCharge) {
 					// is already exists - check doubleConn: { from: [ neuronIdx, neuronIdx ], to: neuronIdx }
 					var existingConn = _state.doubleConn.reduce(function (ret, dblConn) {
@@ -143,17 +146,8 @@ function makeNeuronStore() {
 						// if (dblConn.from[1] === overcharged.idx && dblConn.from[0] === secondNeuronWithCharge.idx) return dblConn;
 						return ret;
 					}, null)
-					var TODO__value = [firstNeuron.value, _state.neuron[secondNeuronWithCharge.idx].value].join('')
-					var TODO__toNodeIdx = _state.neuron.reduce(function (ret, node, idx) {
-						if (node.value === TODO__value) return idx;
-						return ret;
-					}, null)
-					var TODO__toNode = (null !== TODO__toNodeIdx) ? _state.neuron[TODO__toNodeIdx] : null
-					if (null !== TODO__toNode && null !== existingConn && TODO__toNodeIdx !== existingConn.toNode) {
-						// existingConn = null // Froce create new connection if not the same order
-					}
 					var toNode = _state.neuron[secondNeuronWithCharge.idx]
-					DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2(secondNeuronWithCharge)", { from: firstNeuron.value, to: toNode.value, existingConn, TODO__value, TODO__toNode })
+					DBG1 && console.log("TODO:NeuronStore:forwardAnim:2.1(check overcharged)/neuron.2(secondNeuronWithCharge)", { from: firstNeuron.value, to: toNode.value, existingConn })
 					if (existingConn) { // charge Neuron existingConn.to
 						var idxNewNeuron = existingConn.to
 						chargeRemove({ type: 'neuron', idx: overcharged.idx })
@@ -170,32 +164,53 @@ function makeNeuronStore() {
 							} break;
 						}
 						chargeAdd(overcharged.charge + secondNeuronWithCharge.charge, { type: 'neuron', idx: existingConn.to })
+						// _state.animPos += 1
+						// dischargeAll()
+						_notifySubscribers()
+						return;
 					} else { // create new
 						var newNeuron = makeNeuronFromTwoNeurons(overcharged.idx, overcharged.charge, secondNeuronWithCharge.idx, secondNeuronWithCharge.charge)
-						_state.neuron.push(newNeuron)
-						updateOuputHeight(newNeuron)
-						var idxNewNeuron = _state.neuron.length - 1
-						_state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
-						_state.connection.push({ fromType: 'neuron', fromIdx: secondNeuronWithCharge.idx, toIdx: idxNewNeuron, timesCreated: 1 })
-						_state.doubleConn.push({ from: [overcharged.idx, secondNeuronWithCharge.idx], to: idxNewNeuron })
-						chargeRemove({ type: 'neuron', idx: overcharged.idx })
-						chargeRemove({ type: 'neuron', idx: secondNeuronWithCharge.idx })
-						switch (_config.config_strategy_overcharge) {
-							case "REMOVE_CHARGE": break;
-							case "LEAVE_HALF_CHARGE": {
-								chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
-								chargeAdd(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx })
-							} break;
-							case "LEAVE_ALMOST_MAX": {
-								chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
-								chargeAdd(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx })
-							} break;
+						if (!newNeuron) {
+							DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2.1(!newNeuron)", { overcharged })
+
+							// _state.animPos += 1
+							dischargeAll()
+							_notifySubscribers()
+							return;
+						} else {
+							_state.neuron.push(newNeuron)
+							updateOuputHeight(newNeuron)
+							var idxNewNeuron = _state.neuron.length - 1
+							_state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
+							_state.connection.push({ fromType: 'neuron', fromIdx: secondNeuronWithCharge.idx, toIdx: idxNewNeuron, timesCreated: 1 })
+							_state.doubleConn.push({ from: [overcharged.idx, secondNeuronWithCharge.idx], to: idxNewNeuron, connIdx: [_state.connection.length - 2, _state.connection.length - 1] })
+							chargeRemove({ type: 'neuron', idx: overcharged.idx })
+							chargeRemove({ type: 'neuron', idx: secondNeuronWithCharge.idx })
+							switch (_config.config_strategy_overcharge) {
+								case "REMOVE_CHARGE": break;
+								case "LEAVE_HALF_CHARGE": {
+									chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
+									chargeAdd(secondNeuronWithCharge.charge / 2, { type: 'neuron', idx: secondNeuronWithCharge.idx })
+								} break;
+								case "LEAVE_ALMOST_MAX": {
+									// chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
+									// chargeAdd(getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx), { type: 'neuron', idx: secondNeuronWithCharge.idx })
+									var fromCharge = getNeuronAlmostMaxCharge(overcharged.idx)
+									var toCharge = getNeuronAlmostMaxCharge(secondNeuronWithCharge.idx)
+									chargeAdd((fromCharge + toCharge) / 2, { type: 'dbl_conn', idx: _state.doubleConn.length - 1, fromCharge: fromCharge, toCharge: toCharge })
+								} break;
+							}
+							// _state.animPos += 1
+							// dischargeAll()
+							_notifySubscribers()
+							return;
 						}
 					}
 
-					// _state.animPos += 1
-					_notifySubscribers()
-					return;
+					// // _state.animPos += 1
+					// // dischargeAll()
+					// _notifySubscribers()
+					// return;
 				} else { // !secondNeuronWithCharge
 					// TODO: check if already exists! if yes then charge else create new
 					var idxFoundConnection = _state.connection.reduce(function (ret, conn, idx) {
@@ -206,19 +221,31 @@ function makeNeuronStore() {
 					DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2(!secondNeuronWithCharge)", { idxFoundConnection })
 					if (-1 === idxFoundConnection) {
 						var newNeuron = makeNeuronFromOneNeuron(overcharged.idx, overcharged.charge)
-						_state.neuron.push(newNeuron)
-						updateOuputHeight(newNeuron)
-						var idxNewNeuron = _state.neuron.length - 1
-						_state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
-						chargeRemove({ type: 'neuron', idx: overcharged.idx })
-						switch (_config.config_strategy_overcharge) {
-							case "REMOVE_CHARGE": break;
-							case "LEAVE_HALF_CHARGE": {
-								chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
-							} break;
-							case "LEAVE_ALMOST_MAX": {
-								chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
-							} break;
+						if (!newNeuron) {
+							DBG1 && console.log("TODO:NeuronStore:forwardAnim:2(check overcharged)/neuron.2.1(!newNeuron)", { overcharged })
+
+							// _state.animPos += 1
+							dischargeAll()
+							_notifySubscribers()
+							return;
+						} else {
+							_state.neuron.push(newNeuron)
+							updateOuputHeight(newNeuron)
+							var idxNewNeuron = _state.neuron.length - 1
+							_state.connection.push({ fromType: 'neuron', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
+							chargeRemove({ type: 'neuron', idx: overcharged.idx })
+							switch (_config.config_strategy_overcharge) {
+								case "REMOVE_CHARGE": break;
+								case "LEAVE_HALF_CHARGE": {
+									chargeAdd(overcharged.charge / 2, { type: 'neuron', idx: overcharged.idx })
+								} break;
+								case "LEAVE_ALMOST_MAX": {
+									chargeAdd(getNeuronAlmostMaxCharge(overcharged.idx), { type: 'neuron', idx: overcharged.idx })
+								} break;
+							}
+
+							_notifySubscribers()
+							return;
 						}
 					} else {
 						_state.connection[idxFoundConnection].timesCreated += 1
@@ -235,7 +262,13 @@ function makeNeuronStore() {
 						}
 						chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
 					}
+
+					_notifySubscribers()
+					return;
 				}
+
+				// _notifySubscribers()
+				// return;
 			}
 
 			if (overchargedReceptorIdx.length > 0) { // TODO: while ?
@@ -256,34 +289,35 @@ function makeNeuronStore() {
 							_state.connection.push({ fromType: 'receptor', fromIdx: overcharged.idx, toIdx: idxNewNeuron, timesCreated: 1 })
 							idxFoundConnection = _state.connection.length - 1
 							chargeRemove({ type: 'receptor', idx: overcharged.idx })
-							switch (_config.config_strategy_overcharge) {
-								case "REMOVE_CHARGE": break;
-								case "LEAVE_HALF_CHARGE": {
-									chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
-								} break;
-								case "LEAVE_ALMOST_MAX": {
-									chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
-								} break;
-							}
+							// switch (_config.config_strategy_overcharge) {
+							//     case "REMOVE_CHARGE": break;
+							//     case "LEAVE_HALF_CHARGE": {
+							//         chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
+							//     } break;
+							//     case "LEAVE_ALMOST_MAX": {
+							//         chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
+							//     } break;
+							// }
 							chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
 						} else {
 							_state.connection[idxFoundConnection].timesCreated += 1
 							// chargeMove(overcharged.charge, { type: 'receptor', idx: overcharged.idx }, { type: 'connection', idx: idxFoundConnection })
 							chargeRemove({ type: 'receptor', idx: overcharged.idx })
-							switch (_config.config_strategy_overcharge) {
-								case "REMOVE_CHARGE": break;
-								case "LEAVE_HALF_CHARGE": {
-									chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
-								} break;
-								case "LEAVE_ALMOST_MAX": {
-									chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
-								} break;
-							}
+							// switch (_config.config_strategy_overcharge) {
+							//     case "REMOVE_CHARGE": break;
+							//     case "LEAVE_HALF_CHARGE": {
+							//         chargeAdd(overcharged.charge / 2, { type: 'receptor', idx: overcharged.idx })
+							//     } break;
+							//     case "LEAVE_ALMOST_MAX": {
+							//         chargeAdd(getReceptorAlmostMaxCharge(overcharged.idx), { type: 'receptor', idx: overcharged.idx })
+							//     } break;
+							// }
 							chargeAdd(overcharged.charge, { type: 'connection', idx: idxFoundConnection })
 						}
 					}
 
 					// _state.animPos += 1
+					// dischargeAll()
 					_notifySubscribers()
 					return;
 				} else {
@@ -296,33 +330,48 @@ function makeNeuronStore() {
 
 		{ // 3. move all charges (Receptor -> Connection, Connection -> Neuron, Neuron -> Connection)
 			DBG1 && console.log("TODO:NeuronStore:forwardAnim:3(move all charges)", { charge: [].concat(_state.charge) })
-			var toAddChargeNeuronIdxList = _state.charge.filter(function (charge) { return ("connection" === charge.nodeType); }).map(function (charge) {
-				return { charge: charge.charge, connIdx: charge.nodeIdx };
+			var isChargeTypeConnection = function (charge) {
+				if ("connection" === charge.nodeType) return true;
+				if ("dbl_conn" === charge.nodeType) return true;
+				return false;
+			}
+			var toAddChargeNeuronIdxList = _state.charge.filter(isChargeTypeConnection).map(function (charge) {
+				DBG1 && console.log("TODO:NeuronStore:forwardAnim:3(move all charges).LOOP", { charge })
+				switch (charge.nodeType) {
+					case "connection": return { charge: charge.charge, toIdx: _state.connection[charge.nodeIdx].toIdx };
+					case "dbl_conn": return { charge: charge.charge, toIdx: _state.doubleConn[charge.nodeIdx].to };
+				}
+				return null;
 			})
 
-			_state.charge = _state.charge.filter(function (charge) { return !("connection" === charge.nodeType); })
+			_state.charge = _state.charge.filter(function (charge) { return !isChargeTypeConnection(charge); })
 			toAddChargeNeuronIdxList.forEach(function (toAddCharge) {
-				var conn = _state.connection[toAddCharge.connIdx]
-				chargeAdd(toAddCharge.charge, { type: 'neuron', idx: conn.toIdx })
+				chargeAdd(toAddCharge.charge, { type: 'neuron', idx: toAddCharge.toIdx })
 			})
 		}
 		{ // 4. discharge all
-			_state.charge = _state.charge.map(function (charge) {
-				return Object.assign({}, charge, {
-					charge: Math.max(0, charge.charge - _config.config_discharge_per_tick),
-				})
-			}).filter(function (charge) {
-				return charge.charge > 0;
-			})
+			dischargeAll()
 		}
 
 		_state.animPos += 1
 		_notifySubscribers();
 	}
+	function dischargeAll() {
+		_state.charge = _state.charge.map(function (charge) {
+			return Object.assign({}, charge, {
+				charge: Math.max(0, charge.charge - _config.config_discharge_per_tick),
+			})
+		}).filter(function (charge) {
+			return charge.charge > 0;
+		})
+	}
 	function makeNeuronFromTwoNeurons(fromNeuronIdx, fromNeuronCharge, toNeuronIdx, toNeuronCharge) {
 		var fromNode = _state.neuron[fromNeuronIdx]
 		var toNode = _state.neuron[toNeuronIdx]
-		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { fromNode, toNode })
+		var maxCharge = getNewNeuronMaxCharge(fromNode)
+		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { fromNode, toNode, maxCharge })
+		if (maxCharge < _config.config_discharge_per_tick) return null;
+
 		var uiNewNodePos = { cx: 0, cy: 0 }
 		{ // closer to node with more charge (first)
 			var xDiff = Math.abs(fromNode.ui.cx - toNode.ui.cx)
@@ -332,21 +381,25 @@ function makeNeuronStore() {
 			var xToFirst = (toNeuronCharge * xDiff) / (fromNeuronCharge + toNeuronCharge)
 			uiNewNodePos.cx = fromNode.ui.cx + (isFirstOnRight ? -1 : 1) * xToFirst
 		}
+		uiNewNodePos.cy = (fromNode.ui.cy != toNode.ui.cy)
+			? Math.max(fromNode.ui.cy, toNode.ui.cy) + 10 + _config.ui_space_y / 2
+			: fromNode.ui.cy + 10 + _config.ui_space_y
+			;
 		uiNewNodePos.cy = _state.neuron.reduce(function (ret, neuron) {
 			if (uiNewNodePos.cx < neuron.ui.cx - 10) return ret;
 			if (uiNewNodePos.cx > neuron.ui.cx + 10) return ret;
 			if (ret < neuron.ui.cy - 10) return ret;
 			if (ret > neuron.ui.cy + 10) return ret;
 			return ret + 10 + _config.ui_space_y;
-		}, fromNode.ui.cy + 10 + _config.ui_space_y)
+		}, uiNewNodePos.cy)
 
-		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { uiNewNodePos, fromNode, toNode })
+		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromTwoNeurons", { uiNewNodePos, fromNode, toNode, newNodeMaxCharge: getNewNeuronMaxCharge(fromNode) })
 		// var value = '' + (_state.neuron.length + 1);
 		var value = [fromNode.value, toNode.value].join('')
 		return Object.assign({}, DEFAULT_NEURON, {
 			value: value,
 			charge: 0,
-			maxCharge: fromNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
+			maxCharge: getNewNeuronMaxCharge(fromNode),
 			uiShape: "ellipse",
 			ui: Object.assign({}, uiNewNodePos, {
 				rx: 10,
@@ -359,11 +412,11 @@ function makeNeuronStore() {
 		// var value = '' + (_state.neuron.length + 1);
 		var value = [sourceNode.value, ""].join('') // TODO: same name what Receptor
 
-		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneReceptor", { sourceNode, confMaxCharge: _config.config_discharge_max_in_new_neuron_from_one })
+		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneReceptor", { sourceNode, newNodeMaxCharge: getNewNeuronMaxCharge(sourceNode) })
 		return Object.assign({}, DEFAULT_NEURON, {
 			value: value,
 			charge: 0,
-			maxCharge: sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
+			maxCharge: getNewNeuronMaxCharge(sourceNode),
 			uiShape: "ellipse",
 			ui: {
 				cx: sourceNode.ui.cx,
@@ -377,12 +430,14 @@ function makeNeuronStore() {
 		var sourceNode = _state.neuron[fromNeuronIdx]
 		// var value = '' + (_state.neuron.length + 1);
 		var value = [sourceNode.value, "^"].join('')
+		var maxCharge = getNewNeuronMaxCharge(sourceNode)
+		if (maxCharge < _config.config_discharge_per_tick) return null;
 
-		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneNeuron", { sourceNode, confMaxCharge: _config.config_discharge_max_in_new_neuron_from_one })
+		DBG1 && console.log("DBG:NeuronStore:makeNeuronFromOneNeuron", { sourceNode, newNodeMaxCharge: maxCharge })
 		return Object.assign({}, DEFAULT_NEURON, {
 			value: value,
 			charge: 0,
-			maxCharge: sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one,
+			maxCharge: maxCharge,
 			uiShape: "ellipse",
 			ui: {
 				cx: sourceNode.ui.cx,
@@ -392,6 +447,10 @@ function makeNeuronStore() {
 			},
 		});
 	}
+	function getNewNeuronMaxCharge(sourceNode) {
+		// return sourceNode.maxCharge * _config.config_discharge_max_in_new_neuron_from_one;
+		return sourceNode.maxCharge - _config.config_discharge_per_tick;
+	}
 	function chargeAdd(charge, to) {
 		var foundChargeIdx = _state.charge.reduce(function (ret, charge, idx) {
 			if (charge.nodeType === to.type && charge.nodeIdx === to.idx) return idx;
@@ -404,6 +463,12 @@ function makeNeuronStore() {
 		} else {
 			_state.charge[foundChargeIdx].charge += charge
 		}
+		if ('dbl_conn' === to.type) {
+			if (!_state.charge[foundChargeIdx].hasOwnProperty('fromCharge')) _state.charge[foundChargeIdx].fromCharge = 0
+			if (!_state.charge[foundChargeIdx].hasOwnProperty('toCharge')) _state.charge[foundChargeIdx].toCharge = 0
+			_state.charge[foundChargeIdx].fromCharge += to.fromCharge
+			_state.charge[foundChargeIdx].toCharge += to.toCharge
+		}
 		DBG1 && console.log("DBG:NeuronStore:chargeAdd.2", { state_charge: [].concat(_state.charge) })
 		return foundChargeIdx;
 	}
@@ -460,22 +525,6 @@ function makeNeuronStore() {
 			next: [],
 		})
 	}
-	function stateReset(inputText, config) {
-		_inputText = inputText
-		_config = config
-		_state = makeDefaultNeuronsStoreState()
-
-		var distinct = function (value, idx, self) {
-			return (idx === self.indexOf(value));
-		};
-		var foundLetters = inputText.split("").filter(distinct);
-		_state.receptor = foundLetters.map(makeReceptor);
-		_state.mapReceptorChar = _state.receptor.map(function (node) { return node.value; });
-
-		if (_animIntervalID) clearInterval(_animIntervalID)
-
-		_notifySubscribers();
-	}
 	function getReceptorIdx(char) {
 		return _state.mapReceptorChar.indexOf(char)
 	}
@@ -544,6 +593,8 @@ function makeNeuronStore() {
 		getReceptor: function (idx) { return _state.receptor[idx]; },
 		getNeuron: function (idx) { return _state.neuron[idx]; },
 		getConnection: function (idx) { return _state.connection[idx]; },
+		getDoubleConnection: function (idx) { return _state.doubleConn[idx]; },
+		getCharge: function (idx) { return _state.charge[idx]; },
 		getListConnection: function () { return _state.connection; },
 		getListCharge: function () { return _state.charge; },
 		getReceptorCharge: getReceptorCharge,