@@ -22,11 +22,11 @@ interface VaultEnv {
2222 nodes : Map < string , GraphNode >
2323 relationships : NodeRelationship [ ]
2424 settings : EGVSettings
25- getN ( ) : number // Total number of notes
26- getT ( ) : number // Total number of unique tags
27- getK ( ) : number // Average tags per note
28- getOptimalN ( E_max ?: number ) : number // Calculate optimal note count based on formula
29- isPastSingularity ( E_max ?: number ) : boolean // Check if we're hitting Mermaid limits
25+ getN ( ) : number
26+ getT ( ) : number
27+ getK ( ) : number
28+ getOptimalN ( E_max ?: number ) : number
29+ isTearing ( E_max ?: number ) : boolean
3030}
3131
3232export class VaultWhisperer {
@@ -108,7 +108,7 @@ export class VaultWhisperer {
108108 const k = this . getK ( )
109109 return Math . floor ( Math . sqrt ( ( 2 * E_max * t ) / Math . max ( 1 , k ) ) )
110110 } ,
111- isPastSingularity ( E_max : number = 150 ) : boolean {
111+ isTearing ( E_max : number = 150 ) : boolean {
112112 const N = this . getN ( )
113113 const T = this . getT ( )
114114 const K = this . getK ( )
@@ -353,7 +353,7 @@ export class VaultWhisperer {
353353 relationships : [ ...e ] ,
354354 }
355355
356- if ( tempEnv . isPastSingularity ( ) ) {
356+ if ( tempEnv . isTearing ( ) ) {
357357 console . log ( 'Singularity detected - applying backoff strategy' )
358358 this . backoffSingularity ( vAndW , e , vaultEnv )
359359 } else {
@@ -446,136 +446,107 @@ export class VaultWhisperer {
446446 vaultEnv . relationships = e
447447 }
448448
449- // Mermaid specific to not tear the mermaid chart
449+ // Mermaid-specific task when the initial mermaid graph object is tearing
450+ // Creates a reduced graph G'(V',E') where |V'| + |E'| ≤ MAX_TOTAL_ELEMENTS - BRIDGING PATTERN
450451 private backoffSingularity (
451452 nodes : Map < string , GraphNode > ,
452453 relationships : NodeRelationship [ ] ,
453454 vaultEnv : VaultEnv
454455 ) : void {
455- // Strict limits for Mermaid rendering
456- const MAX_TOTAL_ELEMENTS = 100 // Total nodes + relationships
457- const MAX_NODES = 40 // Maximum number of nodes
458- const MAX_RELATIONSHIPS = 60 // Maximum number of relationships
456+ const MAX_NODES = 40
457+ const MAX_RELATIONSHIPS = 60
458+ const MAX_TAGS = 10
459459
460- console . log (
461- 'Performing aggressive graph reduction for Mermaid compatibility'
462- )
460+ console . log ( 'Starting backoff task' )
463461
464- // Step 1: Build tag importance metrics
465- const tagClusters = new Map < string , string [ ] > ( )
466- const tagImportance = new Map < string , number > ( )
462+ // Build the count of {E(v,w)}
463+ const optimalVAndW = new Map < string , string [ ] > ( )
467464
468- // Group notes by tag
469465 relationships . forEach ( ( rel ) => {
470- const targetNode = nodes . get ( rel . target )
471- if ( targetNode ?. type === 'tag' ) {
472- if ( ! tagClusters . has ( rel . target ) ) {
473- tagClusters . set ( rel . target , [ ] )
466+ const target = vaultEnv . nodes . get ( rel . target )
467+ if ( target ?. type === 'tag' ) {
468+ if ( ! optimalVAndW . has ( rel . target ) ) {
469+ optimalVAndW . set ( rel . target , [ ] )
474470 }
475- tagClusters . get ( rel . target ) ! . push ( rel . source )
471+ optimalVAndW . get ( rel . target ) ! . push ( rel . source )
476472 }
477473 } )
478474
479- // Calculate tag importance: weighted by cluster size and connectivity potential
480- tagClusters . forEach ( ( notes , tagId ) => {
481- const clusterSize = notes . length
482- // Tags with moderate-sized clusters are most valuable (not too small, not too large)
483- const connectivityValue = Math . min ( clusterSize , 10 ) // Cap value to avoid huge clusters dominating
484- const importanceScore =
485- connectivityValue * Math . log ( clusterSize + 1 )
486- tagImportance . set ( tagId , importanceScore )
487- } )
488-
489- // Step 2: Select optimal number of tags based on our formula
490- const targetTagCount = Math . min ( 10 , Math . ceil ( MAX_NODES / 4 ) )
491-
492- const selectedTags = Array . from ( tagClusters . entries ( ) )
493- . sort (
494- ( a , b ) =>
495- ( tagImportance . get ( b [ 0 ] ) || 0 ) -
496- ( tagImportance . get ( a [ 0 ] ) || 0 )
497- )
498- . slice ( 0 , targetTagCount )
475+ // Select top tags by the amount of notes pointing to them - most important tags at the front of the pack
476+ const survivingTags = Array . from ( optimalVAndW . entries ( ) )
477+ . sort ( ( a , b ) => b [ 1 ] . length - a [ 1 ] . length )
478+ . slice ( 0 , MAX_TAGS )
499479 . map ( ( [ tagId ] ) => tagId )
500480
501- // Step 3: Select most important notes per tag
502- const selectedNotes = new Set < string > ( )
503- const processedRelationships : NodeRelationship [ ] = [ ]
504-
505- // Distribute note quota among selected tags
506- const notesPerTag =
507- Math . floor ( MAX_NODES - selectedTags . length ) / selectedTags . length
508-
509- selectedTags . forEach ( ( tagId ) => {
510- const notesForTag = tagClusters . get ( tagId ) || [ ]
511-
512- // Get most connected notes (those with most tags/connections)
513- const noteConnectionCounts = new Map < string , number > ( )
514-
515- notesForTag . forEach ( ( noteId ) => {
516- relationships . forEach ( ( rel ) => {
517- if ( rel . source === noteId ) {
518- noteConnectionCounts . set (
519- noteId ,
520- ( noteConnectionCounts . get ( noteId ) || 0 ) + 1
521- )
522- }
523- } )
481+ // Score the E where E = (v_important, w) w being the notes - if we're looking for a bridging pattern in the overall mermaid chart
482+ const noteScores = new Map < string , number > ( )
483+ survivingTags . forEach ( ( tagId ) => {
484+ optimalVAndW . get ( tagId ) ?. forEach ( ( noteId ) => {
485+ noteScores . set ( noteId , ( noteScores . get ( noteId ) || 0 ) + 1 )
524486 } )
525-
526- // Select top notes for this tag
527- const topNotesForTag = Array . from ( notesForTag )
528- . sort (
529- ( a , b ) =>
530- ( noteConnectionCounts . get ( b ) || 0 ) -
531- ( noteConnectionCounts . get ( a ) || 0 )
532- )
533- . slice ( 0 , Math . max ( 2 , Math . ceil ( notesPerTag ) ) )
534-
535- // Add to selected notes
536- topNotesForTag . forEach ( ( noteId ) => selectedNotes . add ( noteId ) )
537487 } )
538488
539- // Step 4: Build final graph with strict limits
540- const finalNodes = new Map < string , GraphNode > ( )
541- const finalRelationships : NodeRelationship [ ] = [ ]
489+ // Select top notes based on E(v_important, w_most)
490+ const survivingNotes = new Set (
491+ Array . from ( noteScores . entries ( ) )
492+ . sort ( ( a , b ) => b [ 1 ] - a [ 1 ] )
493+ . slice ( 0 , MAX_NODES - survivingTags . length )
494+ . map ( ( [ noteId ] ) => noteId )
495+ )
542496
543- // Add selected tag nodes
544- selectedTags . forEach ( ( tagId ) => {
545- const tagNode = nodes . get ( tagId )
546- if ( tagNode ) finalNodes . set ( tagId , tagNode )
497+ // Rebuild graph
498+ nodes . clear ( )
499+ relationships . length = 0
500+
501+ survivingTags . forEach ( ( tagId ) => {
502+ const v = vaultEnv . nodes . get ( tagId )
503+ if ( v ) nodes . set ( tagId , v )
547504 } )
548505
549- // Add selected note nodes
550- selectedNotes . forEach ( ( noteId ) => {
551- const noteNode = nodes . get ( noteId )
552- if ( noteNode ) finalNodes . set ( noteId , noteNode )
506+ survivingNotes . forEach ( ( noteId ) => {
507+ const w = vaultEnv . nodes . get ( noteId )
508+ if ( w ) nodes . set ( noteId , w )
553509 } )
554510
555- // Add relationships, but only between selected nodes
556- relationships . forEach ( ( rel ) => {
557- if ( finalNodes . has ( rel . source ) && finalNodes . has ( rel . target ) ) {
558- finalRelationships . push ( rel )
511+ // Rebuild relationships for graph
512+ let E = new Set < string > ( )
559513
560- // If we exceed relationship limit, stop adding
561- if ( finalRelationships . length >= MAX_RELATIONSHIPS ) {
562- return
514+ // First pass: tag-note connections
515+ vaultEnv . relationships . forEach ( ( rel ) => {
516+ if (
517+ nodes . has ( rel . source ) &&
518+ nodes . has ( rel . target ) &&
519+ E . size < MAX_RELATIONSHIPS
520+ ) {
521+ const sourceIsNote =
522+ vaultEnv . nodes . get ( rel . source ) ?. type !== 'tag'
523+ const targetIsTag =
524+ vaultEnv . nodes . get ( rel . target ) ?. type === 'tag'
525+
526+ if ( sourceIsNote && targetIsTag ) {
527+ relationships . push ( rel )
528+ E . add ( `${ rel . source } -${ rel . target } ` )
563529 }
564530 }
565531 } )
566532
567- // Final verification
568- const totalElements = finalNodes . size + finalRelationships . length
533+ // Second pass: remaining connections
534+ vaultEnv . relationships . forEach ( ( rel ) => {
535+ const relId = `${ rel . source } -${ rel . target } `
536+ if (
537+ nodes . has ( rel . source ) &&
538+ nodes . has ( rel . target ) &&
539+ ! E . has ( relId ) &&
540+ E . size < MAX_RELATIONSHIPS
541+ ) {
542+ relationships . push ( rel )
543+ E . add ( relId )
544+ }
545+ } )
546+
569547 console . log (
570- `Final reduced graph: ${ finalNodes . size } nodes, ${ finalRelationships . length } relationships (${ totalElements } total elements)`
548+ `Final reduced graph: ${ nodes . size } nodes, ${ relationships . length } relationships (${ nodes . size + relationships . length } total elements)`
571549 )
572-
573- // Apply our changes
574- nodes . clear ( )
575- finalNodes . forEach ( ( node , key ) => nodes . set ( key , node ) )
576-
577- relationships . length = 0
578- relationships . push ( ...finalRelationships )
579550 }
580551
581552 private prune (
@@ -600,7 +571,7 @@ export class VaultWhisperer {
600571 filteredNodes . forEach ( ( node , key ) => nodes . set ( key , node ) )
601572 }
602573
603- // Applies DOT-specific features
574+ // Some f(Weight(v,w), Format = .dot)
604575 private runDotSettings ( graph : Graph ) : void {
605576 // Respect relationship weight setting
606577 if ( this . settings . includeWeights && this . settings . weightThreshold ) {
@@ -622,44 +593,29 @@ export class VaultWhisperer {
622593 } )
623594 }
624595
625- // Applies Mermaid-specific features
596+ // Some f(Weight(v,w), Format = .mmd)
626597 private runMMDSettings ( graph : Graph ) : void {
627598 // Respect max-relationships-per-note setting
628599 if ( this . settings . maxEPerV ) {
629- const outgoingArrows = new Map < string , number > ( )
630-
631- // Count outgoing edges per node
632- graph . relationships . forEach ( ( rel ) => {
633- outgoingArrows . set (
634- rel . source ,
635- ( outgoingArrows . get ( rel . source ) || 0 ) + 1
636- )
637- } )
600+ const survivors : NodeRelationship [ ] = [ ]
601+ const cutoff = new Map < string , number > ( )
638602
639- // Filter relationships for notes with too many connections
640- const filteredArrows : NodeRelationship [ ] = [ ]
641- const processedArrows = new Map < string , number > ( )
642-
643- // Sort by weight first to keep the most important relationships
644- const sortedRelationships = [ ...graph . relationships ] . sort (
603+ // Most weighted notes at the front of the pack to survive any pruning
604+ const survivingECandidates = [ ...graph . relationships ] . sort (
645605 ( a , b ) => b . weight - a . weight
646606 )
647607
648- for ( const rel of sortedRelationships ) {
649- const sourceKey = rel . source
650- const currentCount = processedArrows . get ( sourceKey ) || 0
651-
652- if (
653- currentCount < this . settings . maxEPerV ||
654- ( outgoingArrows . get ( sourceKey ) || 0 ) <=
655- this . settings . maxEPerV
656- ) {
657- filteredArrows . push ( rel )
658- processedArrows . set ( sourceKey , currentCount + 1 )
608+ // Add important edges until we reach the mavEPerV cutoff point
609+ for ( const e of survivingECandidates ) {
610+ const eCount = cutoff . get ( e . source ) || 0
611+
612+ if ( eCount < this . settings . maxEPerV ) {
613+ survivors . push ( e )
614+ cutoff . set ( e . source , eCount + 1 )
659615 }
660616 }
661617
662- graph . relationships = filteredArrows
618+ graph . relationships = survivors
663619 }
664620 }
665621
0 commit comments