/** * Crossover Operator — 交叉重组两个基因组 * * 按维度分类处理: * 可合并维度: patternLibrary, scarTissue, toolFluency → 合并去重 * 不可合并维度: mentalModels, judgment, taste → 选择较优父本 * * 来自 09-evolution-algorithm.md 的设计 */ const evolutionQueries = require('../queries').evolutionQueries; // ════════════════════════════════════════════════════════ // 合并策略 // ════════════════════════════════════════════════════════ /** * 合并两个数组,按 key 函数去重 */ function mergeArrays(arr1 = [], arr2 = [], keyFn = JSON.stringify) { const seen = new Set(); const result = []; for (const item of [...arr1, ...arr2]) { const key = keyFn(item); if (!seen.has(key)) { seen.add(key); result.push(item); } } return result; } /** * 合并两个对象,内部数组合并去重 */ function mergeObjects(obj1 = {}, obj2 = {}) { const result = { ...obj1 }; for (const [key, value] of Object.entries(obj2)) { if (Array.isArray(value)) { result[key] = mergeArrays(result[key] || [], value, item => typeof item === 'string' ? item : JSON.stringify(item)); } else if (typeof value === 'object' && value !== null) { result[key] = mergeObjects(result[key] || {}, value); } else if (!(key in result)) { result[key] = value; } } return result; } /** * 选择较优父本的维度(基于该维度的适应度分数) */ function selectBetter(dim1, dim2, score1, score2) { if (score1 === undefined && score2 === undefined) { // 都没有分数,选内容更丰富的 const size1 = JSON.stringify(dim1).length; const size2 = JSON.stringify(dim2).length; return size1 >= size2 ? dim1 : dim2; } return (score1 || 0) >= (score2 || 0) ? dim1 : dim2; } // ════════════════════════════════════════════════════════ // 交叉算子 // ════════════════════════════════════════════════════════ function parseDim(raw) { if (!raw) return {}; return typeof raw === 'string' ? JSON.parse(raw) : raw; } function parseScores(raw) { if (!raw) return {}; return typeof raw === 'string' ? JSON.parse(raw) : raw; } /** * 交叉两个基因组,生成子代 * * @param {object} parent1 - 父本 1 (skill_genomes 行) * @param {object} parent2 - 父本 2 (skill_genomes 行) * @returns {object} 子代基因组数据 (不含 id/run_id 等元数据) */ function crossover(parent1, parent2) { const scores1 = parseScores(parent1.fitness_scores); const scores2 = parseScores(parent2.fitness_scores); // 可合并维度: 合并去重 const patternLibrary = mergeObjects( parseDim(parent1.pattern_library), parseDim(parent2.pattern_library) ); const scarTissue = mergeObjects( parseDim(parent1.scar_tissue), parseDim(parent2.scar_tissue) ); const toolFluency = mergeObjects( parseDim(parent1.tool_fluency), parseDim(parent2.tool_fluency) ); // 不可合并维度: 选择较优 const mentalModels = selectBetter( parseDim(parent1.mental_models), parseDim(parent2.mental_models), scores1.mentalModels, scores2.mentalModels ); const judgment = selectBetter( parseDim(parent1.judgment), parseDim(parent2.judgment), scores1.judgment, scores2.judgment ); const taste = selectBetter( parseDim(parent1.taste), parseDim(parent2.taste), scores1.taste, scores2.taste ); return { parentIds: [parent1.id, parent2.id], mentalModels, patternLibrary, judgment, toolFluency, taste, scarTissue, }; } // ════════════════════════════════════════════════════════ // 公开 API // ════════════════════════════════════════════════════════ /** * 交叉两个基因组并将子代写入数据库 */ async function crossoverAndInsert(parent1Id, parent2Id, runId, generation) { const parent1 = await evolutionQueries.getGenome(parent1Id); const parent2 = await evolutionQueries.getGenome(parent2Id); if (!parent1) throw new Error(`Parent1 ${parent1Id} not found`); if (!parent2) throw new Error(`Parent2 ${parent2Id} not found`); const childData = crossover(parent1, parent2); const child = await evolutionQueries.insertGenome({ runId, skillName: parent1.skill_name, // 同 Skill 才能交叉 generation, ...childData, }); return child; } /** * 对当前代所有同名 Skill 进行随机配对交叉 */ async function crossoverGeneration(runId, generation) { const genomes = await evolutionQueries.getGenomesByRun(runId, { generation: generation - 1, aliveOnly: true, }); // 按 Skill 分组 const bySkill = {}; for (const g of genomes) { if (!bySkill[g.skill_name]) bySkill[g.skill_name] = []; bySkill[g.skill_name].push(g); } const children = []; for (const [skillName, pool] of Object.entries(bySkill)) { if (pool.length < 2) continue; // 按适应度排序 pool.sort((a, b) => (b.overall_fitness || 0) - (a.overall_fitness || 0)); // 精英 × 随机 配对 const elite = pool[0]; for (let i = 1; i < Math.min(pool.length, 3); i++) { const child = await crossoverAndInsert(elite.id, pool[i].id, runId, generation); children.push(child); console.log(` 🔀 ${skillName}: ${elite.id.slice(0, 8)} × ${pool[i].id.slice(0, 8)} → ${child.id.slice(0, 8)}`); } } return children; } module.exports = { crossover, crossoverAndInsert, crossoverGeneration, mergeArrays, mergeObjects, selectBetter, };