From 3ebc54ef74e08d83763202c46ae0b8413adafd34 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Mon, 5 Aug 2019 17:06:31 +0100
Subject: [PATCH] Add unified training mode, but apparently we're now crashing
 the client :thinking:

---
 server/bootstrap/cli.mjs      | 17 +++++++++++++++-
 server/help.mjs               |  7 +++++--
 server/settings.default.toml  |  6 ++++++
 server/train-ai/AITrainer.mjs | 38 ++++++++++++++++++++++++++++++++++-
 4 files changed, 64 insertions(+), 4 deletions(-)

diff --git a/server/bootstrap/cli.mjs b/server/bootstrap/cli.mjs
index 9277d56..931496c 100644
--- a/server/bootstrap/cli.mjs
+++ b/server/bootstrap/cli.mjs
@@ -18,6 +18,7 @@ export default async function(c) {
 	// 2: CLI Argument Parsing
 	let args = process.argv.slice(2); // Slice out the node binary name and the filename
 	let extras = [];
+	
 	for(let i = 0; i < args.length; i++) {
 		if(!args[i].startsWith("-")) {
 			extras.push(args[i]);
@@ -35,6 +36,13 @@ export default async function(c) {
 			case "-v":
 				console.log(program.version);
 				break;
+				
+			case "--ai-unified":
+				settings.ai.mode = "unified";
+				break;
+			case "--ai-split":
+				settings.ai.mode = "split";
+				break;
 			
 			// Add more arguments here
 		}
@@ -99,7 +107,14 @@ export default async function(c) {
 			
 			l.log(`${a.fgreen}${a.hicol}Training AIs${a.reset}`);
 			let ai_trainer = c.cradle.AITrainer;
-			await ai_trainer.train_all();
+			switch(settings.ai.mode) {
+				case "split":
+					await ai_trainer.train_all();
+					break;
+				case "unified":
+					await ai_trainer.train_unified();
+					break;
+			}
 			break;
 		
 		case "geojson-debug":
diff --git a/server/help.mjs b/server/help.mjs
index 15c7152..abaf222 100644
--- a/server/help.mjs
+++ b/server/help.mjs
@@ -17,8 +17,11 @@ ${a.fblue}${a.hicol}Subcommands:${a.reset}
     ${a.fyellow}train-ai${a.reset}          Trains the AI on the consolidated data
 
 ${a.fblue}${a.hicol}Options:${a.reset}
-    ${a.fyellow}-h	--help		${a.reset}Show this message
-    ${a.fyellow}-v	--version	${a.reset}Show the version of this program
+    ${a.fyellow}-h	--help		${a.reset}  Show this message
+    ${a.fyellow}-v	--version	${a.reset}  Show the version of this program
+    
+    ${a.fyellow}    --ai-unified${a.reset}  Switch to the unified AI training mode. This trains 1 AI for everything, instead of 1 AI per gateway.
+    ${a.fyellow}    --ai-split${a.reset}    Switch to the split AI training mode (the default). This trains 1 AI per gateway.
 
 ${a.fblue}${a.hicol}Environment Variables:${a.reset}
     (none yet)
diff --git a/server/settings.default.toml b/server/settings.default.toml
index 3ed6ae5..82c1264 100644
--- a/server/settings.default.toml
+++ b/server/settings.default.toml
@@ -84,6 +84,12 @@ learning_rate = 0.1
 # The momentum term to use when learning
 momentum = 0.1
 
+# Either "split" or "unified".
+# Describes the mode of training for the AIs.
+# In split mode (the default), 1 AI is trained for each of the gateways detected.
+# In unified mode, we train 1 AI that predict everything.
+mode = "split"
+
 # The directory to output trained AIs to, relative to the repository root.
 output_directory = "app/ais/"
 
diff --git a/server/train-ai/AITrainer.mjs b/server/train-ai/AITrainer.mjs
index e99bf3d..7048777 100644
--- a/server/train-ai/AITrainer.mjs
+++ b/server/train-ai/AITrainer.mjs
@@ -15,7 +15,43 @@ class AITrainer {
 		this.repo_gateway = GatewayRepo;
 	}
 	
+	async train_unified() {
+		this.l.log(`${this.a.fgreen}${this.a.hicol}Unified training mode activated.${this.a.reset}`);
+		let filepath = path.join(
+			this.root_dir,
+			"..",
+			this.settings.ai.output_directory,
+			"ai.json"
+		);
+		
+		if(!fs.existsSync(path.dirname(filepath)))
+			await fs.promises.mkdir(path.dirname(filepath), { recursive: true });
+		
+		let training_result = await this.train_gateway(null, filepath);
+		
+		await fs.promises.writeFile(
+			path.join(
+				path.dirname(this.root_dir),
+				this.settings.ai.output_directory,
+				"index.json"
+			),
+			JSON.stringify({
+				properties: {
+					rssi_min: -150,
+					rssi_max:  0
+				},
+				index: [{
+					id: "Unified AI (not a real gateway)",
+					filename: "ai.json",
+					latitude: 0, longitude: 0,
+					net_settings: training_result.net_settings
+				}]
+			}, null, "\t")
+		);
+	}
+	
 	async train_all() {
+		this.l.log(`${this.a.fgreen}${this.a.hicol}Split (default) training mode activated.${this.a.reset}`);
 		let index = [];
 		for(let gateway of this.repo_gateway.iterate()) {
 			let filepath = path.join(this.root_dir, "..", this.settings.ai.output_directory, `${gateway.id}.json`);
@@ -87,7 +123,7 @@ class AITrainer {
 			timeout: Infinity
 		});
 		
-		await fs.promises.writeFile(destination_filename, JSON.stringify(net.toJSON()));
+		await fs.promises.writeFile(destination_filename, JSON.stringify(net.toJSON()), null, "\t");
 		// console.log(result);
 		
 		return {
