summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Headless/Program.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Headless/Program.fs')
-rw-r--r--src/LivingVillage.Headless/Program.fs97
1 files changed, 61 insertions, 36 deletions
diff --git a/src/LivingVillage.Headless/Program.fs b/src/LivingVillage.Headless/Program.fs
index be9c325..bcf2e41 100644
--- a/src/LivingVillage.Headless/Program.fs
+++ b/src/LivingVillage.Headless/Program.fs
@@ -175,6 +175,48 @@ let gini (xs: int64[]) : float =
num <- num + abs (float xi - float xj)
num / (2.0 * n * n * (s / n))
+type WorldVerdict =
+ { Line: string
+ Ok: bool
+ CheckOld: bool
+ Ratio: float
+ Gini: float }
+
+let evalWorld (days: int64) (k: int) : WorldVerdict =
+ let seed = 42UL + uint64 k
+ let stats = runSimulation false days seed Sim.npcCount
+ let m = Sim.relationMatrix stats.World
+ let n = Array2D.length1 m
+ let mutable hasNonFinite = stats.NonFinite > 0L
+ for i in 0 .. n - 1 do
+ for j in 0 .. n - 1 do
+ if Single.IsNaN m.[i, j] || Single.IsInfinity m.[i, j] then hasNonFinite <- true
+ let chats = stats.ChatsPerNpc
+ let chatsTotal = Array.sum chats
+ let chatsAvg = float chatsTotal / float chats.Length
+ let chatsMax = if chats.Length > 0 then Array.max chats else 0L
+ let ratio = if chatsAvg > 0.0 then float chatsMax / chatsAvg else 0.0
+ let g = gini chats
+ let counts = Sim.relationCounts m
+ let cntMax = if counts.Length > 0 then Array.max counts else 0
+ let cntMin = if counts.Length > 0 then Array.min counts else 0
+ let connected = Array.fold (fun acc c -> if c > 0 then acc + 1 else acc) 0 counts
+ let connectedNeed = int (System.Math.Ceiling(0.6 * float chats.Length))
+ let checkA = ratio > 2.0
+ let checkB = connected >= connectedNeed
+ let checkC = not hasNonFinite
+ let checkOld = cntMax > 2 * cntMin
+ let ok = checkA && checkB && checkC
+ let line =
+ sprintf "world=%d seed=%d chats_max=%d chats_avg=%.2f ratio=%.3f gini=%.3f relcnt_max=%d relcnt_min=%d relconn=%d/%d nonfinite=%d check_a=%s check_b=%s check_c=%s old_crit=%s %s"
+ k seed chatsMax chatsAvg ratio g cntMax cntMin connected connectedNeed stats.NonFinite
+ (if checkA then "PASS" else "FAIL")
+ (if checkB then "PASS" else "FAIL")
+ (if checkC then "PASS" else "FAIL")
+ (if checkOld then "PASS" else "FAIL")
+ (if ok then "OK" else "BAD")
+ { Line = line; Ok = ok; CheckOld = checkOld; Ratio = ratio; Gini = g }
+
let runBatch (worlds: int) (days: int64) : int =
printfn "batch worlds=%d days=%d half_life_ticks=%d rel_threshold=%.2f" worlds days Sim.relationHalfLifeTicks Sim.relationThreshold
// 判据语义(M3c 修正):
@@ -184,48 +226,31 @@ let runBatch (worlds: int) (days: int64) : int =
// 明星集中型分布过严,relcnt_max=0 即判死,与"非均匀分布"的验收目标不符)
// (c) 无 NaN/Inf
printfn "checks: (a) chats max/avg > 2.0 (b) npcs_with_relation >= 0.6*npc_count (c) no NaN (old) relcnt_max > 2*relcnt_min (report only)"
+ // 世界间相互独立(seed=42+k,逐世界确定性),可安全并行;输出仍按 world 序号
+ // 顺序打印,逐行内容与串行版本逐字节一致。LV_BATCH_WORKERS 可覆盖(默认 modest 4)。
+ let workers =
+ match Environment.GetEnvironmentVariable "LV_BATCH_WORKERS" with
+ | null | "" -> min 4 Environment.ProcessorCount
+ | v -> match Int32.TryParse v with true, n when n > 0 -> min n Environment.ProcessorCount | _ -> min 4 Environment.ProcessorCount
+ printfn "workers=%d" workers
+ let results = Array.zeroCreate worlds
+ System.Threading.Tasks.Parallel.For
+ (0, worlds,
+ System.Threading.Tasks.ParallelOptions(MaxDegreeOfParallelism = workers),
+ fun k -> results.[k] <- evalWorld days k)
+ |> ignore
let mutable passed = 0
let mutable failed = 0
let mutable oldPassed = 0
let ratios = ResizeArray<float> ()
let ginis = ResizeArray<float> ()
let sw = System.Diagnostics.Stopwatch.StartNew()
- for k in 0 .. worlds - 1 do
- let seed = 42UL + uint64 k
- let stats = runSimulation false days seed Sim.npcCount
- let m = Sim.relationMatrix stats.World
- let n = Array2D.length1 m
- let mutable hasNonFinite = stats.NonFinite > 0L
- for i in 0 .. n - 1 do
- for j in 0 .. n - 1 do
- if Single.IsNaN m.[i, j] || Single.IsInfinity m.[i, j] then hasNonFinite <- true
- let chats = stats.ChatsPerNpc
- let chatsTotal = Array.sum chats
- let chatsAvg = float chatsTotal / float chats.Length
- let chatsMax = if chats.Length > 0 then Array.max chats else 0L
- let ratio = if chatsAvg > 0.0 then float chatsMax / chatsAvg else 0.0
- let g = gini chats
- let counts = Sim.relationCounts m
- let cntMax = if counts.Length > 0 then Array.max counts else 0
- let cntMin = if counts.Length > 0 then Array.min counts else 0
- let connected = Array.fold (fun acc c -> if c > 0 then acc + 1 else acc) 0 counts
- let connectedNeed = int (System.Math.Ceiling(0.6 * float chats.Length))
- let checkA = ratio > 2.0
- let checkB = connected >= connectedNeed
- let checkC = not hasNonFinite
- let checkOld = cntMax > 2 * cntMin
- let ok = checkA && checkB && checkC
- if ok then passed <- passed + 1 else failed <- failed + 1
- if checkOld then oldPassed <- oldPassed + 1
- ratios.Add ratio
- ginis.Add g
- printfn "world=%d seed=%d chats_max=%d chats_avg=%.2f ratio=%.3f gini=%.3f relcnt_max=%d relcnt_min=%d relconn=%d/%d nonfinite=%d check_a=%s check_b=%s check_c=%s old_crit=%s %s"
- k seed chatsMax chatsAvg ratio g cntMax cntMin connected connectedNeed stats.NonFinite
- (if checkA then "PASS" else "FAIL")
- (if checkB then "PASS" else "FAIL")
- (if checkC then "PASS" else "FAIL")
- (if checkOld then "PASS" else "FAIL")
- (if ok then "OK" else "BAD")
+ for r in results do
+ printfn "%s" r.Line
+ if r.Ok then passed <- passed + 1 else failed <- failed + 1
+ if r.CheckOld then oldPassed <- oldPassed + 1
+ ratios.Add r.Ratio
+ ginis.Add r.Gini
sw.Stop()
let allPass = failed = 0
printfn "batch_summary worlds=%d passed=%d failed=%d old_passed=%d/%d ratio_min=%.3f ratio_max=%.3f gini_min=%.3f gini_max=%.3f elapsed_s=%.1f"