From dbe58008b294c43b505bf121b92de953238263e7 Mon Sep 17 00:00:00 2001 From: Tyler Camp Date: Mon, 6 May 2024 18:04:47 -0400 Subject: [PATCH] Various solver optimizations, reduced runtime + memory usage --- PalCalc.Model/EnumerableExtensions.cs | 11 +++- PalCalc.Solver/BreedingSolver.cs | 48 ++++++++++++---- PalCalc.Solver/IPalReference.cs | 79 +++++++++++++++++++-------- PalCalc.Solver/WorkingSet.cs | 3 +- 4 files changed, 102 insertions(+), 39 deletions(-) diff --git a/PalCalc.Model/EnumerableExtensions.cs b/PalCalc.Model/EnumerableExtensions.cs index e8d8dad..65c60b2 100644 --- a/PalCalc.Model/EnumerableExtensions.cs +++ b/PalCalc.Model/EnumerableExtensions.cs @@ -59,11 +59,16 @@ public static V GetValueOrElse(this IDictionary dict, K key, V fallb public static int SetHash(this IEnumerable elements) { - var baseHash = elements.Count(); + var baseHash = 0; + var total = 0; foreach (var g in elements.GroupBy(e => e.GetHashCode()).OrderBy(g => g.Key)) - baseHash = HashCode.Combine(baseHash, g.Key, g.Count()); + { + var count = g.Count(); + baseHash = HashCode.Combine(baseHash, g.Key, total); + total += count; + } - return baseHash; + return HashCode.Combine(baseHash, total); } } } diff --git a/PalCalc.Solver/BreedingSolver.cs b/PalCalc.Solver/BreedingSolver.cs index e775e8e..58d68df 100644 --- a/PalCalc.Solver/BreedingSolver.cs +++ b/PalCalc.Solver/BreedingSolver.cs @@ -102,20 +102,46 @@ static List RelevantInstancesForTraits(PalDB db, List // gender (if they're wild/bred pals) for the least overall effort (different pals have different gender probabilities) (IPalReference, IPalReference) PreferredParentsGenders(IPalReference parent1, IPalReference parent2) { - List ParentOptions(IPalReference parent) => parent.Gender == PalGender.WILDCARD - ? new List() { parent.WithGuaranteedGender(db, PalGender.MALE), parent.WithGuaranteedGender(db, PalGender.FEMALE) } - : new List() { parent }; + IEnumerable ParentOptions(IPalReference parent) + { + if (parent.Gender == PalGender.WILDCARD) + { + yield return parent.WithGuaranteedGender(db, PalGender.MALE); + yield return parent.WithGuaranteedGender(db, PalGender.FEMALE); + } + else + { + yield return parent; + } + } var optionsParent1 = ParentOptions(parent1); var optionsParent2 = ParentOptions(parent2); - var parentPairOptions = optionsParent1.SelectMany(p1v => optionsParent2.Where(p2v => p2v.IsCompatibleGender(p1v.Gender)).Select(p2v => (p1v, p2v))).ToList(); + var parentPairOptions = optionsParent1 + .SelectMany(p1v => optionsParent2.Where(p2v => p2v.IsCompatibleGender(p1v.Gender)).Select(p2v => (p1v, p2v))) + //.ToList() + ; Func CombinedEffortFunc = gameSettings.MultipleBreedingFarms ? ((a, b) => a.BreedingEffort > b.BreedingEffort ? a.BreedingEffort : b.BreedingEffort) : ((a, b) => a.BreedingEffort + b.BreedingEffort); - if (parentPairOptions.Select(pair => CombinedEffortFunc(pair.p1v, pair.p2v)).Distinct().Count() == 1) + TimeSpan optimalTime = TimeSpan.Zero; + bool hasNoPreference = true; + foreach (var (p1, p2) in parentPairOptions) + { + var effort = CombinedEffortFunc(p1, p2); + if (optimalTime == TimeSpan.Zero) optimalTime = effort; + else if (optimalTime != effort) + { + hasNoPreference = false; + + if (effort < optimalTime) optimalTime = effort; + } + } + + if (hasNoPreference) { // either there is no preference or at least 1 parent already has a specific gender var p1wildcard = parent1.Gender == PalGender.WILDCARD; @@ -144,7 +170,7 @@ List ParentOptions(IPalReference parent) => parent.Gender == PalG } else { - return parentPairOptions.MinBy(p => CombinedEffortFunc(p.p1v, p.p2v)); + return parentPairOptions.First(p => optimalTime == CombinedEffortFunc(p.p1v, p.p2v)); } } @@ -401,7 +427,7 @@ var traitGroup in palGroup var combinedTraits = p.Item1.EffectiveTraits.Concat(p.Item2.EffectiveTraits); - var anyRelevantFromParents = spec.Traits.Intersect(combinedTraits).Any(); + var anyRelevantFromParents = combinedTraits.Intersect(spec.Traits).Any(); var anyIrrelevantFromParents = combinedTraits.Except(spec.Traits).Any(); return anyRelevantFromParents || !anyIrrelevantFromParents; @@ -440,19 +466,17 @@ var traitGroup in palGroup // (not entirely correct, since some irrelevant traits may be specific and inherited by parents. if we know a child // may have some specific trait, it may be efficient to breed that child with another parent which also has that // irrelevant trait, which would increase the overall likelyhood of a desired trait being inherited) - var potentialIrrelevantTraits = Enumerable - .Range(0, Math.Max(0, numFinalTraits - desiredParentTraits.Count)) - .Select(i => new RandomTrait()); - var newTraits = new List(numFinalTraits); newTraits.AddRange(desiredParentTraits); + while (newTraits.Count < numFinalTraits) + newTraits.Add(new RandomTrait()); var res = new BredPalReference( gameSettings, db.BreedingByParent[parent1.Pal][parent2.Pal].Child, preferredParent1, preferredParent2, - desiredParentTraits.Concat(potentialIrrelevantTraits).ToList(), + newTraits, probabilityForUpToNumTraits ); diff --git a/PalCalc.Solver/IPalReference.cs b/PalCalc.Solver/IPalReference.cs index b480aa5..712287c 100644 --- a/PalCalc.Solver/IPalReference.cs +++ b/PalCalc.Solver/IPalReference.cs @@ -1,5 +1,6 @@ using PalCalc.Model; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; @@ -131,14 +132,19 @@ public CompositeOwnedPalReference(OwnedPalReference male, OwnedPalReference fema public TimeSpan SelfBreedingEffort { get; } = TimeSpan.Zero; + private CompositeOwnedPalReference oppositeWildcardReference; public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) { switch (gender) { case PalGender.MALE: return Male; case PalGender.FEMALE: return Female; - case PalGender.OPPOSITE_WILDCARD: return new CompositeOwnedPalReference(Male, Female) { Gender = gender }; case PalGender.WILDCARD: return this; + case PalGender.OPPOSITE_WILDCARD: + if (oppositeWildcardReference == null) + oppositeWildcardReference = new CompositeOwnedPalReference(Male, Female) { Gender = gender }; + return oppositeWildcardReference; + default: throw new NotImplementedException(); } } @@ -189,12 +195,8 @@ public int CapturesRequiredForGender public int EffectiveTraitsHash { get; } - public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) + private IPalReference WithGuaranteedGenderImpl(PalDB db, PalGender gender) { - if (Gender != PalGender.WILDCARD) throw new Exception("Wild pal has already been given a guaranteed gender"); - - if (gender == PalGender.WILDCARD) return this; - return new WildPalReference(Pal) { SelfBreedingEffort = SelfBreedingEffort, @@ -203,6 +205,26 @@ public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) }; } + private Dictionary cachedGuaranteedGenders = null; + public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) + { + if (Gender != PalGender.WILDCARD) throw new Exception("Wild pal has already been given a guaranteed gender"); + + if (gender == PalGender.WILDCARD) return this; + + if (cachedGuaranteedGenders == null) + { + cachedGuaranteedGenders = new List() + { + PalGender.MALE, + PalGender.FEMALE, + PalGender.OPPOSITE_WILDCARD + }.ToDictionary(g => g, g => WithGuaranteedGenderImpl(db, g)); + } + + return cachedGuaranteedGenders[gender]; + } + public override string ToString() => $"Captured {Gender} {Pal} w/ up to {EffectiveTraits.Count} random traits"; } @@ -227,6 +249,12 @@ private BredPalReference(GameSettings gameSettings, Pal pal, IPalReference paren } EffectiveTraits = traits; EffectiveTraitsHash = traits.SetHash(); + + parentBreedingEffort = gameSettings.MultipleBreedingFarms && Parent1 is BredPalReference && Parent2 is BredPalReference + ? Parent1.BreedingEffort > Parent2.BreedingEffort + ? Parent1.BreedingEffort + : Parent2.BreedingEffort + : Parent1.BreedingEffort + Parent2.BreedingEffort; } public BredPalReference(GameSettings gameSettings, Pal pal, IPalReference parent1, IPalReference parent2, List traits, float traitsProbability) : this(gameSettings, pal, parent1, parent2, traits) @@ -250,14 +278,9 @@ public BredPalReference(GameSettings gameSettings, Pal pal, IPalReference parent public int AvgRequiredBreedings { get; private set; } public TimeSpan SelfBreedingEffort => AvgRequiredBreedings * gameSettings.AvgBreedingTime; - public TimeSpan BreedingEffort => SelfBreedingEffort + ( - gameSettings.MultipleBreedingFarms && Parent1 is BredPalReference && Parent2 is BredPalReference - ? Parent1.BreedingEffort > Parent2.BreedingEffort - ? Parent1.BreedingEffort - : Parent2.BreedingEffort - : Parent1.BreedingEffort + Parent2.BreedingEffort - ); + private TimeSpan parentBreedingEffort; + public TimeSpan BreedingEffort => SelfBreedingEffort + parentBreedingEffort; private int numTotalBreedingSteps = -1; public int NumTotalBreedingSteps @@ -277,10 +300,8 @@ public int NumTotalBreedingSteps public List ActualTraits => EffectiveTraits; - public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) + private IPalReference WithGuaranteedGenderImpl(PalDB db, PalGender gender) { - if (this.Gender != PalGender.WILDCARD) throw new Exception("Cannot change gender of bred pal with an already-guaranteed gender"); - if (gender == PalGender.WILDCARD) { return this; @@ -298,14 +319,16 @@ public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) TraitsProbability = TraitsProbability, }; } - - // no preferred bred gender, i.e. 50/50 bred chance, so have half the probability / twice the effort to get desired instance - return new BredPalReference(gameSettings, Pal, Parent1, Parent2, EffectiveTraits) + else { - AvgRequiredBreedings = AvgRequiredBreedings * 2, - Gender = gender, - TraitsProbability = TraitsProbability, - }; + // no preferred bred gender, i.e. 50/50 bred chance, so have half the probability / twice the effort to get desired instance + return new BredPalReference(gameSettings, Pal, Parent1, Parent2, EffectiveTraits) + { + AvgRequiredBreedings = AvgRequiredBreedings * 2, + Gender = gender, + TraitsProbability = TraitsProbability, + }; + } } else { @@ -319,6 +342,16 @@ public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) } } + private ConcurrentDictionary cachedGuaranteedGenders = null; + public IPalReference WithGuaranteedGender(PalDB db, PalGender gender) + { + if (this.Gender != PalGender.WILDCARD) throw new Exception("Cannot change gender of bred pal with an already-guaranteed gender"); + + if (cachedGuaranteedGenders == null) cachedGuaranteedGenders = new ConcurrentDictionary(); + + return cachedGuaranteedGenders.GetOrAdd(gender, (gender) => WithGuaranteedGenderImpl(db, gender)); + } + public override string ToString() => $"Bred {Gender} {Pal} w/ ({EffectiveTraits.TraitsListToString()})"; public override bool Equals(object obj) diff --git a/PalCalc.Solver/WorkingSet.cs b/PalCalc.Solver/WorkingSet.cs index 6036244..6f25bb4 100644 --- a/PalCalc.Solver/WorkingSet.cs +++ b/PalCalc.Solver/WorkingSet.cs @@ -49,7 +49,8 @@ public bool Process(Func, IEnumerable