@@ -24,7 +24,14 @@ const MAX_LOAD_BALANCE_INTERVAL: usize = 90;
2424// cache. a flat 10% increase over the max interval should be enough to not have
2525// problems
2626const LOAD_BALANCE_DAYS : usize = ( MAX_LOAD_BALANCE_INTERVAL as f32 * 1.1 ) as usize ;
27- const SIBLING_PENALTY : f32 = 0.001 ;
27+ // when bury siblings is enabled, we try and make it so siblings are not
28+ // scheduled on the same days. a day with a sibling is set to a very low
29+ // (non-zero to make algorithms simpler) weight. to further disperse siblings,
30+ // closer days are given lower weights. days right before/after have a 0.2
31+ // weight modifier, 2 days before/after have a 0.4 weight modifier, etc
32+ const SIBLING_MODIFIER_STEPS : [ i32 ; 11 ] = [ -5 , -4 , -3 , -2 , -1 , 0 , 1 , 2 , 3 , 4 , 5 ] ;
33+ const SIBLING_MODIFIER_RANGE : [ f32 ; 11 ] =
34+ [ 1.0 , 0.8 , 0.6 , 0.4 , 0.2 , 0.000001 , 0.2 , 0.4 , 0.6 , 0.8 , 1.0 ] ;
2835
2936#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
3037pub enum EasyDay {
@@ -137,7 +144,6 @@ impl LoadBalancer {
137144 HashMap :: < _ , Vec < _ > > :: new ( ) ,
138145 |mut day_group_by_dcid, ( cid, nid, dcid) | {
139146 day_group_by_dcid. entry ( dcid) . or_default ( ) . push ( ( cid, nid) ) ;
140-
141147 day_group_by_dcid
142148 } ,
143149 )
@@ -237,25 +243,23 @@ impl LoadBalancer {
237243 let easy_days_load = self . easy_days_percentages_by_preset . get ( & deckconfig_id) ?;
238244 let easy_days_modifier =
239245 calculate_easy_days_modifiers ( easy_days_load, & weekdays, & review_counts) ;
240-
241- let intervals = interval_days
242- . iter ( )
246+ let sibling_modifier =
247+ calculate_sibling_modifiers ( & self . days_by_preset , before_days, after_days, note_id) ;
248+ let intervals = review_counts
249+ . into_iter ( )
250+ . zip ( easy_days_modifier)
251+ . zip ( sibling_modifier)
243252 . enumerate ( )
244- . map ( |( interval_index, interval_day) | {
245- LoadBalancerInterval {
246- target_interval : interval_index as u32 + before_days,
247- review_count : review_counts[ interval_index] ,
248- // if there is a sibling on this day, give it a very low weight
249- sibling_modifier : note_id
250- . and_then ( |note_id| {
251- interval_day
252- . has_sibling ( & note_id)
253- . then_some ( SIBLING_PENALTY )
254- } )
255- . unwrap_or ( 1.0 ) ,
256- easy_days_modifier : easy_days_modifier[ interval_index] ,
257- }
258- } ) ;
253+ . map (
254+ |( interval_index, ( ( review_count, easy_days_modifier) , sibling_modifier) ) | {
255+ LoadBalancerInterval {
256+ target_interval : interval_index as u32 + before_days,
257+ review_count,
258+ sibling_modifier,
259+ easy_days_modifier,
260+ }
261+ } ,
262+ ) ;
259263
260264 select_weighted_interval ( intervals, fuzz_seed)
261265 }
@@ -352,6 +356,59 @@ pub(crate) fn calculate_easy_days_modifiers(
352356 . collect ( )
353357}
354358
359+ // gently nudge siblings so they don't clump together
360+ // all deckconfigs need to be searched, rather than just the day, because
361+ // siblings might have different deckconfigs.
362+ // for example, a card is being scheduled and days 2 and 8 have siblings:
363+ // X X
364+ // days: 0 1 2 3 4 5 6 7 8 9 10
365+ // 2 mod: 0.40 0.20 0.00 0.20 0.40 0.60 0.80
366+ // 8 mod: 0.80 0.60 0.40 0.20 0.00 0.20 0.40
367+ // total: 0.40 0.20 0.00 0.20 0.32 0.36 0.32 0.20 0.00 0.20 0.40
368+ // 0.00 is actually a not-actually-zero-but-really small number for ease of
369+ // implementation
370+ fn calculate_sibling_modifiers (
371+ days_by_preset : & HashMap < DeckConfigId , [ LoadBalancerDay ; LOAD_BALANCE_DAYS ] > ,
372+ before_days : u32 ,
373+ after_days : u32 ,
374+ nid : Option < NoteId > ,
375+ ) -> Vec < f32 > {
376+ let mut modifiers = vec ! [ 1.0 ; after_days as usize - before_days as usize + 1 ] ;
377+
378+ if let Some ( nid) = nid {
379+ let sibling_days = days_by_preset
380+ . iter ( )
381+ . flat_map ( |( _did, days) | {
382+ days. iter ( )
383+ . enumerate ( )
384+ . fold ( HashSet :: new ( ) , |mut sibling_days, ( i, day) | {
385+ if day. has_sibling ( & nid) {
386+ sibling_days. insert ( i) ;
387+ }
388+ sibling_days
389+ } )
390+ } )
391+ . collect :: < HashSet < _ > > ( ) ;
392+
393+ for sibling_day in sibling_days {
394+ let sibling_iter = SIBLING_MODIFIER_STEPS
395+ . iter ( )
396+ . zip ( SIBLING_MODIFIER_RANGE . iter ( ) ) ;
397+ for ( step, sibling_modifier_value) in sibling_iter {
398+ // converts true interval to modifier-space
399+ let target_day = sibling_day as i32 + step - before_days as i32 ;
400+ if let Ok ( target_day) = TryInto :: < usize > :: try_into ( target_day) {
401+ if let Some ( day_modifier) = modifiers. get_mut ( target_day) {
402+ * day_modifier *= sibling_modifier_value;
403+ }
404+ }
405+ }
406+ }
407+ }
408+
409+ modifiers
410+ }
411+
355412pub struct LoadBalancerInterval {
356413 pub target_interval : u32 ,
357414 pub review_count : usize ,
0 commit comments