#include "blocks.h" #include #include #include #include #include #include #ifdef _WIN32 #include #define make_dir(p) _mkdir(p) #else #include #define make_dir(p) mkdir(p, 0755) #endif #define N_TEST_GAINS 3 static const float TEST_GAINS[N_TEST_GAINS] = {0.5f, 1.0f, 2.0f}; void block_init(Block *b, int id, int n, const char *name) { memset(b, 0, sizeof *b); b->id = id; b->n = n < 1 ? 1 : n > BLOCK_MAX_NEURONS ? BLOCK_MAX_NEURONS : n; b->gain = 1.0f; b->enabled = true; b->first = b->last = -1; if (name && *name) snprintf(b->name, sizeof b->name, "%s", name); else snprintf(b->name, sizeof b->name, "Blocco %d", id); } static void free_weights(Block *b) { free(b->area.layer); free(b->area.w_in); free(b->area.w_out); free(b->area.bias); free(b->area.scale); free(b->links_out); free(b->links_in); memset(&b->area, 0, sizeof b->area); b->links_out = b->links_in = NULL; } void block_free(Block *b) { free_weights(b); for (int i = 0; i < b->n_examples; i++) free(b->examples[i]); b->n_examples = 0; } // ---- testo dal modello ---------------------------------------------------------- static char *trim(char *s) { while (isspace((unsigned char)*s)) s++; char *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) *--e = 0; return s; } // Toglie numerazione, trattini e virgolette all'inizio e alla fine di una riga. static char *clean_line(char *s) { s = trim(s); while (*s && (isdigit((unsigned char)*s) || strchr("-*.)#:", *s) || isspace((unsigned char)*s))) s++; const char *quotes[] = {"\"", "«", "“", "'"}; for (size_t q = 0; q < 4; q++) if (!strncmp(s, quotes[q], strlen(quotes[q]))) s += strlen(quotes[q]); s = trim(s); const char *ends[] = {"\"", "»", "”"}; for (size_t q = 0; q < 3; q++) { size_t n = strlen(s), m = strlen(ends[q]); if (n >= m && !strcmp(s + n - m, ends[q])) s[n - m] = 0; } return trim(s); } static int ask(Brain *brain, const char *system_prompt, const char *question, float temp, unsigned seed, int max_tokens, char *out, size_t cap) { ChatMsg m[2] = {{ROLE_SYSTEM, system_prompt}, {ROLE_USER, question}}; GenOpts o = {.thinking = false, .max_tokens = max_tokens, .temperature = temp, .seed = seed}; return brain_chat(brain, m, 2, &o, NULL, NULL, out, cap); } static void add_example(Block *b, const char *s) { if (b->n_examples < BLOCK_MAX_EXAMPLES) b->examples[b->n_examples++] = strdup(s); } // ---- misura --------------------------------------------------------------------- typedef struct { float *x, *o; } Capture; // medie per strato [L][E] di ingresso FFN e uscita strato static bool capture_text(Brain *brain, const char *text, int L, int E, Capture *c) { brain_capture_begin(brain); int n = brain_feed(brain, text); long nt; const double *x = brain_capture_x(brain, &nt), *o = brain_capture_out(brain); brain_capture_end(brain); if (n <= 0 || nt <= 0) return false; size_t N = (size_t)L * E; c->x = malloc(N * sizeof(float)); c->o = malloc(N * sizeof(float)); for (size_t i = 0; i < N; i++) { c->x[i] = (float)(x[i] / nt); c->o[i] = (float)(o[i] / nt); } return true; } static float dotf(const float *a, const float *b, int n) { double s = 0; for (int i = 0; i < n; i++) s += (double)a[i] * b[i]; return (float)s; } static void normalize(float *v, int n) { double s = 0; for (int i = 0; i < n; i++) s += (double)v[i] * v[i]; float inv = s > 0 ? (float)(1.0 / sqrt(s)) : 0; for (int i = 0; i < n; i++) v[i] *= inv; } // Quanto la direzione "esempi - neutri" separa i due gruppi in questo strato (d' di Cohen). static float layer_separation(const Capture *pos, int P, const Capture *neg, int N, int l, int E, float *w) { size_t off = (size_t)l * E; for (int d = 0; d < E; d++) { double mp = 0, mn = 0; for (int i = 0; i < P; i++) mp += pos[i].x[off + d]; for (int j = 0; j < N; j++) mn += neg[j].x[off + d]; w[d] = (float)(mp / P - mn / N); } normalize(w, E); double sp = 0, sp2 = 0, sn = 0, sn2 = 0; for (int i = 0; i < P; i++) { double p = dotf(w, pos[i].x + off, E); sp += p; sp2 += p * p; } for (int j = 0; j < N; j++) { double p = dotf(w, neg[j].x + off, E); sn += p; sn2 += p * p; } double mp = sp / P, mn = sn / N; double vp = sp2 / P - mp * mp, vn = sn2 / N - mn * mn; return (float)((mp - mn) / sqrt(0.5 * (vp + vn) + 1e-6)); } // ---- applicazione ----------------------------------------------------------------- bool block_apply(Block *blk, Brain *brain, Synapses *syn, const Atlas *atlas, const char *system_prompt, const char *function, char **user_examples, int n_user_examples, int fix_first, int fix_last, block_progress_fn progress, void *ud, char *err, size_t err_cap) { BrainInfo info = brain_info(brain); const int L = info.n_layer, E = info.n_embd; blk->n_embd = E; char msg[512], q[1600]; size_t cap = 8192; char *out = malloc(cap); bool ok = false; Capture *pos = NULL, *neg = NULL; int P = 0, N = 0; float *w = NULL; char question[400] = ""; brain_area_remove(brain, blk->id); // una nuova funzione sostituisce la precedente free_weights(blk); for (int i = 0; i < blk->n_examples; i++) free(blk->examples[i]); blk->n_examples = 0; blk->applied = false; snprintf(blk->function, sizeof blk->function, "%s", function); // 1. Neuronal studia la funzione: esempi, nome, domanda di prova progress(ud, "Neuronal sta studiando la funzione..."); for (int i = 0; i < n_user_examples; i++) add_example(blk, user_examples[i]); if (blk->n_examples < 6) { snprintf(q, sizeof q, "Nel tuo cervello è appena nato un blocco di %d neuroni. La sua funzione sarà: \"%s\".\n" "Scrivi 10 frasi brevi, concrete e diverse in italiano in cui questa funzione è chiaramente all'opera. " "Una frase per riga, senza numeri, elenchi o virgolette.", blk->n, function); for (int attempt = 0; attempt < 3 && blk->n_examples < 6; attempt++) { if (ask(brain, system_prompt, q, 0.7f, 7u + (unsigned)attempt, 700, out, cap) < 0) break; for (char *line = strtok(out, "\n"); line; line = strtok(NULL, "\n")) { char *s = clean_line(line); if (strlen(s) >= 15 && !text_looks_foreign(s)) add_example(blk, s); } } } if (blk->n_examples < 3) { snprintf(err, err_cap, "Neuronal non è riuscito a elaborare esempi della funzione"); goto done; } if (!strncmp(blk->name, "Blocco ", 7)) { snprintf(q, sizeof q, "Dai un nome breve (massimo 3 parole, in italiano) a un blocco di neuroni con questa funzione: \"%s\". " "Rispondi solo con il nome.", function); if (ask(brain, system_prompt, q, 0, 0, 16, out, cap) >= 0) { char *s = clean_line(strtok(out, "\n") ? out : out); size_t n = strlen(s); if (n && s[n - 1] == '.') s[n - 1] = 0; if (*s && strlen(s) < sizeof blk->name && !text_looks_foreign(s)) snprintf(blk->name, sizeof blk->name, "%s", s); } } snprintf(q, sizeof q, "Scrivi una sola domanda breve in italiano, che un utente potrebbe farti, a cui si risponde meglio " "usando questa funzione: \"%s\". Rispondi solo con la domanda.", function); if (ask(brain, system_prompt, q, 0, 0, 60, out, cap) >= 0) snprintf(question, sizeof question, "%s", clean_line(out)); // 2. lettura degli esempi e di testi neutri (due per categoria della mappa) int n_neutral = 0; for (int c = 0; c < atlas->n_cat; c++) n_neutral += atlas->cat[c].n_prompts < 2 ? atlas->cat[c].n_prompts : 2; snprintf(msg, sizeof msg, "Lettura di %d esempi e confronto con %d testi neutri...", blk->n_examples, n_neutral); progress(ud, msg); pos = calloc((size_t)blk->n_examples, sizeof(Capture)); neg = calloc((size_t)n_neutral, sizeof(Capture)); for (int i = 0; i < blk->n_examples; i++) if (capture_text(brain, blk->examples[i], L, E, &pos[P])) P++; for (int c = 0; c < atlas->n_cat; c++) for (int p = 0; p < atlas->cat[c].n_prompts && p < 2; p++) if (capture_text(brain, atlas->cat[c].prompts[p], L, E, &neg[N])) N++; if (P < 2 || N < 2) { snprintf(err, err_cap, "lettura degli esempi fallita"); goto done; } // 3. strati: finestra contigua dove la funzione si distingue meglio dai testi neutri progress(ud, "Neuronal cerca gli strati in cui la funzione vive..."); int W = blk->n / 16; W = W < 1 ? 1 : W > 6 ? 6 : W; float *sep = calloc((size_t)L, sizeof(float)); w = malloc(sizeof(float) * (size_t)E); int lo = 2, hi = L - 3; // strati ammessi if (fix_first >= 0 && fix_last >= fix_first && fix_last < L) { lo = fix_first; hi = fix_last; } if (W > hi - lo + 1) W = hi - lo + 1; for (int l = lo; l <= hi; l++) sep[l] = layer_separation(pos, P, neg, N, l, E, w); int best = lo; float best_s = -1e30f; for (int s0 = lo; s0 + W - 1 <= hi; s0++) { float s = 0; for (int l = s0; l < s0 + W; l++) s += sep[l]; if (s > best_s) { best_s = s; best = s0; } } blk->first = best; blk->last = best + W - 1; blk->separation = best_s / W; free(sep); // 4. costruzione dei neuroni: ognuno parte da un esempio (o da una miscela di due) snprintf(msg, sizeof msg, "Costruzione di %d neuroni negli strati %d-%d...", blk->n, blk->first, blk->last); progress(ud, msg); BrainArea *A = &blk->area; A->n = blk->n; A->layer = malloc(sizeof(int) * (size_t)blk->n); A->bias = malloc(sizeof(float) * (size_t)blk->n); A->scale = malloc(sizeof(float) * (size_t)blk->n); A->w_in = malloc(sizeof(float) * (size_t)blk->n * E); A->w_out = malloc(sizeof(float) * (size_t)blk->n * E); A->enabled = true; A->tonic = false; int *base = malloc(sizeof(int) * (size_t)blk->n); // esempio principale di ogni neurone float *mix_x = malloc(sizeof(float) * (size_t)E); double pos_sum = 0, neg_sum = 0; for (int k = 0; k < blk->n; k++) { int l = blk->first + k % W; int e1 = (k / W) % P, round = k / (W * P); int e2 = P > 1 ? (e1 + round) % P : e1; float alpha = round == 0 ? 1.0f : 0.35f + 0.3f * (float)((round * 37) % 10) / 10.0f; int per_layer = blk->n / W + (k % W < blk->n % W); size_t off = (size_t)l * E; float *wi = A->w_in + (size_t)k * E, *wo = A->w_out + (size_t)k * E; for (int d = 0; d < E; d++) { double mn = 0, mo = 0, mp = 0; for (int j = 0; j < N; j++) { mn += neg[j].x[off + d]; mo += neg[j].o[off + d]; } for (int i = 0; i < P; i++) mp += pos[i].x[off + d]; mn /= N; mo /= N; mp /= P; mix_x[d] = alpha * pos[e1].x[off + d] + (1 - alpha) * pos[e2].x[off + d]; // direzione comune della funzione + la sfumatura del proprio esempio wi[d] = (float)(0.7 * (mp - mn) + 0.3 * (mix_x[d] - mp)); float o = alpha * pos[e1].o[off + d] + (1 - alpha) * pos[e2].o[off + d]; wo[d] = (float)((o - mo) / ((double)per_layer * W)); // tutto il blocco insieme ≈ una differenza piena } normalize(wi, E); // soglia tarata su tutti gli esempi: neutro medio -> 0, esempio medio -> 1 float pm = 0, nm = 0; for (int i = 0; i < P; i++) pm += dotf(wi, pos[i].x + off, E); for (int j = 0; j < N; j++) nm += dotf(wi, neg[j].x + off, E); pm /= P; nm /= N; A->layer[k] = l; A->bias[k] = nm + 0.15f * (pm - nm); A->scale[k] = pm > A->bias[k] ? 1.0f / (pm - A->bias[k]) : 0.0f; base[k] = e1; for (int i = 0; i < P; i++) { float a = (dotf(wi, pos[i].x + off, E) - A->bias[k]) * A->scale[k]; pos_sum += a < 0 ? 0 : a > 1 ? 1 : a; } for (int j = 0; j < N; j++) { float a = (dotf(wi, neg[j].x + off, E) - A->bias[k]) * A->scale[k]; neg_sum += a < 0 ? 0 : a > 1 ? 1 : a; } } blk->pos_rate = (float)(pos_sum / ((double)blk->n * P)); blk->neg_rate = (float)(neg_sum / ((double)blk->n * N)); free(mix_x); // 5. sinapsi: per ogni strato, una passata sulle matrici con i neuroni "base" (uno per esempio) progress(ud, "Collegamento del blocco alle sinapsi della rete..."); blk->links_out = calloc((size_t)blk->n * BLOCK_LINKS, sizeof(SynLink)); blk->links_in = calloc((size_t)blk->n * BLOCK_LINKS, sizeof(SynLink)); if (syn) { float *vo = malloc(sizeof(float) * (size_t)P * E), *vi = malloc(sizeof(float) * (size_t)P * E); SynLink *lo = malloc(sizeof(SynLink) * (size_t)P * BLOCK_LINKS), *li = malloc(sizeof(SynLink) * (size_t)P * BLOCK_LINKS); int *rep = malloc(sizeof(int) * (size_t)P); // neurone rappresentante di ogni esempio nello strato for (int l = blk->first; l <= blk->last; l++) { int nb = 0; for (int e = 0; e < P; e++) { rep[e] = -1; for (int k = 0; k < blk->n; k++) if (A->layer[k] == l && base[k] == e) { rep[e] = k; break; } if (rep[e] < 0) continue; memcpy(vo + (size_t)nb * E, A->w_out + (size_t)rep[e] * E, sizeof(float) * (size_t)E); memcpy(vi + (size_t)nb * E, A->w_in + (size_t)rep[e] * E, sizeof(float) * (size_t)E); rep[nb++] = rep[e]; } syn_vectors_out(syn, l, vo, nb, BLOCK_LINKS, lo); syn_vectors_in(syn, l, vi, nb, BLOCK_LINKS, li); for (int k = 0; k < blk->n; k++) { if (A->layer[k] != l) continue; for (int r = 0; r < nb; r++) if (base[rep[r]] == base[k]) { memcpy(blk->links_out + (size_t)k * BLOCK_LINKS, lo + (size_t)r * BLOCK_LINKS, sizeof(SynLink) * BLOCK_LINKS); memcpy(blk->links_in + (size_t)k * BLOCK_LINKS, li + (size_t)r * BLOCK_LINKS, sizeof(SynLink) * BLOCK_LINKS); break; } } } free(vo); free(vi); free(lo); free(li); free(rep); } free(base); // 6. Neuronal prova il blocco a diverse intensità e sceglie come usarlo blk->gain = TEST_GAINS[1]; blk->applied = true; blk->enabled = true; snprintf(blk->effect, sizeof blk->effect, "Nessuna prova eseguita."); if (*question) { progress(ud, "Neuronal prova il blocco a diverse intensità..."); char answers[N_TEST_GAINS + 1][360]; for (int g = 0; g <= N_TEST_GAINS; g++) { // l'ultima prova è a blocco spento A->gain = g < N_TEST_GAINS ? TEST_GAINS[g] : 0.0f; A->enabled = g < N_TEST_GAINS; brain_area_remove(brain, blk->id); brain_area_add(brain, blk->id, A); answers[g][0] = 0; if (ask(brain, system_prompt, question, 0, 0, 70, out, cap) >= 0) { char *s = trim(out); for (char *c = s; *c; c++) if (*c == '\n') *c = ' '; snprintf(answers[g], sizeof answers[g], "%s", s); } } SBuf j = {0}; sb_printf(&j, "Domanda: \"%s\"\nRisposte possibili:\n", question); for (int g = 0; g <= N_TEST_GAINS; g++) sb_printf(&j, "%c) %s\n", 'A' + g, answers[g]); sb_printf(&j, "\nQuale risposta mostra meglio questa capacità: \"%s\", restando in italiano corretto e sensata? " "Rispondi solo con la lettera.", function); int pick = 1; if (ask(brain, system_prompt, j.p, 0, 0, 4, out, cap) >= 0) { for (char *c = out; *c; c++) { int g = toupper((unsigned char)*c) - 'A'; if (g >= 0 && g <= N_TEST_GAINS && (c == out || !isalpha((unsigned char)c[-1])) && !isalpha((unsigned char)c[1])) { pick = g; break; } } } sb_free(&j); if (pick < N_TEST_GAINS && text_looks_foreign(answers[pick])) pick = 1; if (pick == N_TEST_GAINS) { blk->gain = TEST_GAINS[0]; snprintf(blk->effect, sizeof blk->effect, "Prova: \"%s\". Neuronal ha preferito la risposta senza blocco, quindi lo usa con delicatezza (x%.1f). " "Risposta scelta: \"%.200s\"", question, blk->gain, answers[pick]); } else { blk->gain = TEST_GAINS[pick]; snprintf(blk->effect, sizeof blk->effect, "Prova: \"%s\". Con il blocco a x%.1f: \"%.180s\" | senza blocco: \"%.180s\"", question, blk->gain, answers[pick], answers[N_TEST_GAINS]); } } A->gain = blk->gain; A->enabled = blk->enabled; brain_area_remove(brain, blk->id); brain_area_add(brain, blk->id, A); ok = true; done: for (int i = 0; i < P; i++) { free(pos[i].x); free(pos[i].o); } for (int j = 0; j < N; j++) { free(neg[j].x); free(neg[j].o); } free(pos); free(neg); free(out); free(w); return ok; } // ---- salvataggio ------------------------------------------------------------------ // Un file binario per blocco: /block_.bin #define BLOCK_MAGIC_V2 0x324B4C42u // "BLK2": senza macroarea #define BLOCK_MAGIC 0x334B4C42u // "BLK3" static void path_of(const char *dir, int id, char *out, size_t cap) { snprintf(out, cap, "%s/block_%d.bin", dir, id); } bool block_save(const Block *b, const char *dir) { make_dir(dir); char path[1024]; path_of(dir, b->id, path, sizeof path); FILE *f = fopen(path, "wb"); if (!f) return false; uint32_t magic = BLOCK_MAGIC; int32_t hdr[7] = {b->id, b->n, b->applied, b->enabled, b->first, b->last, b->n_embd}; float fl[4] = {b->gain, b->separation, b->pos_rate, b->neg_rate}; fwrite(&magic, 4, 1, f); fwrite(hdr, sizeof hdr, 1, f); fwrite(fl, sizeof fl, 1, f); int32_t region = b->region; fwrite(®ion, 4, 1, f); fwrite(b->name, sizeof b->name, 1, f); fwrite(b->function, sizeof b->function, 1, f); fwrite(b->effect, sizeof b->effect, 1, f); int32_t ne = b->n_examples; fwrite(&ne, 4, 1, f); for (int i = 0; i < ne; i++) { uint32_t len = (uint32_t)strlen(b->examples[i]); fwrite(&len, 4, 1, f); fwrite(b->examples[i], 1, len, f); } if (b->applied) { size_t n = (size_t)b->n, E = (size_t)b->n_embd; fwrite(b->area.layer, sizeof(int), n, f); fwrite(b->area.bias, sizeof(float), n, f); fwrite(b->area.scale, sizeof(float), n, f); fwrite(b->area.w_in, sizeof(float), n * E, f); fwrite(b->area.w_out, sizeof(float), n * E, f); fwrite(b->links_out, sizeof(SynLink), n * BLOCK_LINKS, f); fwrite(b->links_in, sizeof(SynLink), n * BLOCK_LINKS, f); } bool ok = !ferror(f); fclose(f); return ok; } static bool load_one(const char *path, int n_embd, Block *b) { FILE *f = fopen(path, "rb"); if (!f) return false; uint32_t magic = 0; int32_t hdr[7]; float fl[4]; memset(b, 0, sizeof *b); int32_t region = 0; bool ok = fread(&magic, 4, 1, f) == 1 && (magic == BLOCK_MAGIC || magic == BLOCK_MAGIC_V2) && fread(hdr, sizeof hdr, 1, f) == 1 && fread(fl, sizeof fl, 1, f) == 1 && (magic == BLOCK_MAGIC_V2 || fread(®ion, 4, 1, f) == 1) && fread(b->name, sizeof b->name, 1, f) == 1 && fread(b->function, sizeof b->function, 1, f) == 1 && fread(b->effect, sizeof b->effect, 1, f) == 1; if (ok) { b->id = hdr[0]; b->n = hdr[1]; b->applied = hdr[2]; b->enabled = hdr[3]; b->first = hdr[4]; b->last = hdr[5]; b->n_embd = hdr[6]; b->gain = fl[0]; b->separation = fl[1]; b->pos_rate = fl[2]; b->neg_rate = fl[3]; b->region = region; b->name[sizeof b->name - 1] = b->function[sizeof b->function - 1] = b->effect[sizeof b->effect - 1] = 0; ok = b->n > 0 && b->n <= BLOCK_MAX_NEURONS && (!b->applied || b->n_embd == n_embd); } int32_t ne = 0; if (ok) ok = fread(&ne, 4, 1, f) == 1 && ne >= 0 && ne <= BLOCK_MAX_EXAMPLES; for (int i = 0; ok && i < ne; i++) { uint32_t len = 0; ok = fread(&len, 4, 1, f) == 1 && len < 4096; if (!ok) break; char *s = malloc(len + 1); ok = fread(s, 1, len, f) == len; s[len] = 0; b->examples[b->n_examples++] = s; } if (ok && b->applied) { size_t n = (size_t)b->n, E = (size_t)b->n_embd; BrainArea *A = &b->area; A->n = b->n; A->layer = malloc(sizeof(int) * n); A->bias = malloc(sizeof(float) * n); A->scale = malloc(sizeof(float) * n); A->w_in = malloc(sizeof(float) * n * E); A->w_out = malloc(sizeof(float) * n * E); b->links_out = malloc(sizeof(SynLink) * n * BLOCK_LINKS); b->links_in = malloc(sizeof(SynLink) * n * BLOCK_LINKS); ok = fread(A->layer, sizeof(int), n, f) == n && fread(A->bias, sizeof(float), n, f) == n && fread(A->scale, sizeof(float), n, f) == n && fread(A->w_in, sizeof(float), n * E, f) == n * E && fread(A->w_out, sizeof(float), n * E, f) == n * E && fread(b->links_out, sizeof(SynLink), n * BLOCK_LINKS, f) == n * BLOCK_LINKS && fread(b->links_in, sizeof(SynLink), n * BLOCK_LINKS, f) == n * BLOCK_LINKS; A->gain = b->gain; A->enabled = b->enabled; } fclose(f); if (!ok) block_free(b); return ok; } bool block_file_ok(const char *path, int n_embd) { Block b; if (!load_one(path, n_embd, &b)) return false; block_free(&b); return true; } int block_load_all(const char *dir, int n_embd, Block **out) { *out = NULL; int n = 0, cap = 0; char path[1024]; for (int id = 1; id <= 4096; id++) { // gli id sono piccoli e crescenti path_of(dir, id, path, sizeof path); FILE *probe = fopen(path, "rb"); if (!probe) continue; fclose(probe); if (n == cap) { cap = cap ? cap * 2 : 8; *out = realloc(*out, sizeof(Block) * (size_t)cap); } if (load_one(path, n_embd, &(*out)[n])) n++; else fprintf(stderr, "blocco non leggibile, ignorato: %s\n", path); } return n; } void block_delete_file(const char *dir, int id) { char path[1024]; path_of(dir, id, path, sizeof path); remove(path); } void block_json(const Block *b, SBuf *o, bool links) { sb_printf(o, "{\"id\":%d,\"region\":%d,\"name\":", b->id, b->region); sb_json_str(o, b->name); sb_puts(o, ",\"function\":"); sb_json_str(o, b->function); sb_printf(o, ",\"neurons\":%d,\"applied\":%s,\"enabled\":%s,\"gain\":%.2f,\"first\":%d,\"last\":%d," "\"separation\":%.2f,\"pos_rate\":%.2f,\"neg_rate\":%.2f,\"effect\":", b->n, b->applied ? "true" : "false", b->enabled ? "true" : "false", b->gain, b->first, b->last, b->separation, b->pos_rate, b->neg_rate); sb_json_str(o, b->effect); sb_printf(o, ",\"auto_off\":%s,\"auto_gain\":%.2f,\"forced\":%s,\"auto_reason\":", b->auto_off ? "true" : "false", b->auto_gain, b->forced ? "true" : "false"); sb_json_str(o, b->auto_reason); sb_puts(o, ",\"examples\":["); for (int i = 0; i < b->n_examples; i++) { if (i) sb_puts(o, ","); sb_json_str(o, b->examples[i]); } sb_puts(o, "]"); if (links && b->applied) { sb_puts(o, ",\"layers\":["); for (int k = 0; k < b->n; k++) sb_printf(o, "%s%d", k ? "," : "", b->area.layer[k]); sb_puts(o, "],\"links_out\":["); for (int k = 0; k < b->n * BLOCK_LINKS; k++) { const SynLink *l = &b->links_out[k]; sb_printf(o, "%s[%d,%d,%.4g]", k ? "," : "", l->layer, l->idx, l->w); } sb_puts(o, "],\"links_in\":["); for (int k = 0; k < b->n * BLOCK_LINKS; k++) { const SynLink *l = &b->links_in[k]; sb_printf(o, "%s[%d,%d,%.4g]", k ? "," : "", l->layer, l->idx, l->w); } sb_puts(o, "]"); } sb_puts(o, "}"); }