From 36fc909a8c0da8add3fe7c4e71dfb595cf2f73a1 Mon Sep 17 00:00:00 2001 From: Script Raccoon Date: Fri, 4 Sep 2026 10:33:33 +0200 Subject: [PATCH 1/2] allow several structures in combinations script also: some refactoring --- database/scripts/combinations.ts | 87 +++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/database/scripts/combinations.ts b/database/scripts/combinations.ts index 368d407db..572d1a4d5 100644 --- a/database/scripts/combinations.ts +++ b/database/scripts/combinations.ts @@ -4,20 +4,20 @@ import { remove_underscores } from '$shared/utils' /** * This script prints the combinations of the form p ∧ ¬q - * that are witnessed by a given structure but by no other - * structure in the database. In particular, it can be used - * when adding new structures to the database to detect which - * combinations are newly witnessed. + * that are witnessed by the supplied structures (or their duals) + * but not by any other structure in the database. */ const db = get_client({ readonly: true }) const args = process.argv.slice(2) -const [structure_id, type] = args +const [type, ...structure_ids] = args -if (args.length !== 2 || !structure_id || !type) { - console.error('Expected exactly 2 arguments: .') +if (!type || structure_ids.length === 0) { + console.error( + 'Expected arguments: ...' + ) process.exit(1) } @@ -26,16 +26,17 @@ if (!is_structure_type(type)) { process.exit(1) } -const structure = db - .prepare< - [string, StructureType], - { id: string } - >(`SELECT id FROM structures WHERE id = ? AND type = ?`) - .get(structure_id, type) +const all_structure_ids = db + .prepare<[StructureType], string>(`SELECT id FROM structures WHERE type = ?`) + .pluck() + .all(type) -if (!structure) { +const set_all_structure_ids = new Set(all_structure_ids) +const unknown_structure_ids = structure_ids.filter((id) => !set_all_structure_ids.has(id)) + +if (unknown_structure_ids.length > 0) { console.error( - `No ${remove_underscores(type)} with ID "${structure_id}" exists in the database.` + `No ${remove_underscores(type)} with ID "${unknown_structure_ids[0]}" exists in the database.` ) process.exit(1) } @@ -66,37 +67,63 @@ const combinations = db ) .all(type) -const other_combinations = new Set( +const other_combination_keys: Set = new Set( combinations - .filter((comb) => comb.structure_id !== structure_id) + .filter(({ structure_id }) => !structure_ids.includes(structure_id)) .flatMap(({ p, q }) => combination_keys(p, q)) ) -const unique_combinations = combinations - .filter( - (comb) => - comb.structure_id === structure_id && - combination_keys(comb.p, comb.q).every((key) => !other_combinations.has(key)) - ) - .map(({ p, q }) => ({ p, q })) +const unique_combinations: { p: string; q: string }[] = [] + +for (const { p, q, structure_id } of combinations) { + if ( + structure_ids.includes(structure_id) && + combination_keys(p, q).every((key) => !other_combination_keys.has(key)) && + unique_combinations.every((comb) => comb.p != p || comb.q !== q) + ) { + unique_combinations.push({ p, q }) + } +} + +const dual_combinations: { p: string; q: string }[] = [] + +for (const { p, q } of unique_combinations) { + const dual_p = dual_property_ids.get(p) + const dual_q = dual_property_ids.get(q) + if (!dual_p || !dual_q) continue + + if (unique_combinations.every((comb) => comb.p != dual_p || comb.q != dual_q)) { + dual_combinations.push({ p: dual_p, q: dual_q }) + } +} + +const all_unique_combinations = [...unique_combinations, ...dual_combinations] console.info( - `Found ${unique_combinations.length} unique witnessed combinations for ${remove_underscores(type)} with ID "${structure_id}":` + `Found ${all_unique_combinations.length} unique witnessed combinations by the supplied structures (${structure_ids.join(', ')}):` ) -if (unique_combinations.length === 0) { - console.info('None') +if (all_unique_combinations.length === 0) { + console.info('\nNone') process.exit(0) } +console.info('\nDirectly witnessed:') for (const { p, q } of unique_combinations) { console.info(`- ${p} ∧ ¬${q}`) } +if (dual_combinations.length > 0) { + console.info('\nDually witnessed:') + for (const { p, q } of dual_combinations) { + console.info(`- ${p} ∧ ¬${q}`) + } +} + function combination_keys(p: string, q: string): string[] { - const keys = new Set([`${p}|${q}`]) - const dual_p = dual_property_ids.get(p) ?? null - const dual_q = dual_property_ids.get(q) ?? null + const keys = new Set([`${p}|${q}`]) + const dual_p = dual_property_ids.get(p) + const dual_q = dual_property_ids.get(q) if (dual_p && dual_q) keys.add(`${dual_p}|${dual_q}`) return [...keys] } From 2b2f4880eb00957a23b2fae5d5735e69396f7f52 Mon Sep 17 00:00:00 2001 From: Script Raccoon Date: Fri, 4 Sep 2026 11:31:33 +0200 Subject: [PATCH 2/2] improve performance of combinations script --- database/scripts/combinations.ts | 86 ++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/database/scripts/combinations.ts b/database/scripts/combinations.ts index 572d1a4d5..c289ec628 100644 --- a/database/scripts/combinations.ts +++ b/database/scripts/combinations.ts @@ -52,47 +52,56 @@ const dual_property_ids = new Map( property_duals.map(({ id, dual_property_id }) => [id, dual_property_id]) ) -const combinations = db - .prepare<[StructureType], { structure_id: string; p: string; q: string }>( - `SELECT - a.structure_id, - a.property_id AS p, - an.property_id AS q - FROM property_assignments a - JOIN property_assignments an - ON an.structure_id = a.structure_id AND an.type = a.type - WHERE a.type = ? - AND a.is_satisfied = TRUE - AND an.is_satisfied = FALSE` +const structure_placeholders = structure_ids.map(() => '?').join(', ') + +const unique_combinations = db + .prepare<[StructureType, ...string[]], { p: string; q: string }>( + `WITH selected AS ( + SELECT DISTINCT a.property_id AS p, an.property_id AS q + FROM property_assignments a + JOIN property_assignments an + ON an.structure_id = a.structure_id AND an.type = a.type + WHERE a.type = ? + AND a.structure_id IN (${structure_placeholders}) + AND a.is_satisfied = TRUE + AND an.is_satisfied = FALSE + ) + SELECT selected.p, selected.q + FROM selected + JOIN properties p ON p.id = selected.p AND p.type = ? + JOIN properties q ON q.id = selected.q AND q.type = ? + WHERE NOT EXISTS ( + SELECT 1 + FROM property_assignments a + JOIN property_assignments an + ON an.structure_id = a.structure_id AND an.type = a.type + WHERE a.type = ? + AND a.structure_id NOT IN (${structure_placeholders}) + AND a.is_satisfied = TRUE + AND an.is_satisfied = FALSE + AND ( + (a.property_id = selected.p AND an.property_id = selected.q) + OR (a.property_id = p.dual_property_id AND an.property_id = q.dual_property_id) + ) + )` ) - .all(type) + .all(type, ...structure_ids, type, type, type, ...structure_ids) -const other_combination_keys: Set = new Set( - combinations - .filter(({ structure_id }) => !structure_ids.includes(structure_id)) - .flatMap(({ p, q }) => combination_keys(p, q)) +const unique_combination_keys = new Set( + unique_combinations.map(({ p, q }) => combination_key(p, q)) ) -const unique_combinations: { p: string; q: string }[] = [] - -for (const { p, q, structure_id } of combinations) { - if ( - structure_ids.includes(structure_id) && - combination_keys(p, q).every((key) => !other_combination_keys.has(key)) && - unique_combinations.every((comb) => comb.p != p || comb.q !== q) - ) { - unique_combinations.push({ p, q }) - } -} - const dual_combinations: { p: string; q: string }[] = [] -for (const { p, q } of unique_combinations) { +for (const key of unique_combination_keys) { + const { p, q } = decode_combination_key(key) const dual_p = dual_property_ids.get(p) const dual_q = dual_property_ids.get(q) if (!dual_p || !dual_q) continue - if (unique_combinations.every((comb) => comb.p != dual_p || comb.q != dual_q)) { + const dual_key = combination_key(dual_p, dual_q) + + if (dual_key && !unique_combination_keys.has(dual_key)) { dual_combinations.push({ p: dual_p, q: dual_q }) } } @@ -120,10 +129,13 @@ if (dual_combinations.length > 0) { } } -function combination_keys(p: string, q: string): string[] { - const keys = new Set([`${p}|${q}`]) - const dual_p = dual_property_ids.get(p) - const dual_q = dual_property_ids.get(q) - if (dual_p && dual_q) keys.add(`${dual_p}|${dual_q}`) - return [...keys] +// Helper functions + +function combination_key(p: string, q: string) { + return `${p}|${q}` +} + +function decode_combination_key(key: string) { + const separator = key.indexOf('|') + return { p: key.slice(0, separator), q: key.slice(separator + 1) } }