Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combination generation optimizations #42

Merged
merged 4 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@

/*
28/03/2023:
| Method | Setup | Mean | Error | StdDev | Median |
|------------------------ |--------- |--------------:|-------------:|-------------:|--------------:|
| GenerateAllCombinations | Setup #1 | 29.30 us | 0.495 us | 0.989 us | 28.87 us |
| GenerateAllCombinations | Setup #2 | 12.66 us | 0.115 us | 0.108 us | 12.67 us |
| GenerateAllCombinations | Setup #3 | 217,844.91 us | 4,274.928 us | 8,337.918 us | 218,002.67 us |
| GenerateAllCombinations | Setup #4 | 195,762.60 us | 3,861.216 us | 6,011.449 us | 195,647.20 us |
| GenerateAllCombinations | Setup #5 | 325,507.60 us | 6,469.168 us | 7,449.904 us | 325,091.15 us |
| GenerateAllCombinations | Setup #6 | 1,303.72 us | 16.524 us | 13.798 us | 1,304.00 us |
| Method | Setup | Mean | Error | StdDev |
|------------------------ |--------- |--------------:|-------------:|-------------:|
| GenerateAllCombinations | Setup #1 | 30.10 us | 0.210 us | 0.196 us |
| GenerateAllCombinations | Setup #2 | 16.93 us | 0.138 us | 0.129 us |
| GenerateAllCombinations | Setup #3 | 172,957.46 us | 2,525.541 us | 2,238.826 us |
| GenerateAllCombinations | Setup #4 | 169,320.36 us | 2,768.573 us | 2,589.725 us |
| GenerateAllCombinations | Setup #5 | 310,245.02 us | 5,375.505 us | 5,028.251 us |
| GenerateAllCombinations | Setup #6 | 7,992.88 us | 70.567 us | 66.009 us |
| GenerateAllCombinations | Setup #7 | 113,752.86 us | 2,183.336 us | 2,336.145 us |
*/
public class CombinatorialMachineBenchmark
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ public static IEnumerable<CombinatorialMachineSetup> ConstructObservableCombinat

yield return setup6;

// TODO: Add a setup for which none of the last level utilities satisfy the first level's demands.
// 1000 categories; 100 utilities in each; all utilities in the first category are incompatible with all utilities in the last category.
var setup7 = new CombinatorialMachineSetup("Setup #7");
for (var i = 0; i < 1000; i++) setup7.WithCategory(ConstructCategoryName(i), 100);
for (var i = 1; i <= 100; i++) setup7.WithDemands(ConstructCategoryName(0), i, ConstructCategoryName(999), "q");
yield return setup7;
}

private static string ConstructCategoryName(int letterIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public IEnumerable<TValue> Get(string category)
return registeredUtilities.OrEmptyIfNull();
}

/// <inheritdoc />
public bool ContainsCategory(string category) => this._data.ContainsKey(category);

/// <inheritdoc />
public void Register(string category, TValue value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public interface ICleanTestInitializationCollection<TValue> : IEnumerable<KeyVal
{
IReadOnlyCollection<string> Categories { get; }
IEnumerable<TValue> Get(string category);
bool ContainsCategory(string category);
void Register(string category, TValue value);
IEnumerable<TValue> GetAllValues();
}
79 changes: 45 additions & 34 deletions TryAtSoftware.CleanTests.Core/Utilities/CombinatorialMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using TryAtSoftware.CleanTests.Core.Extensions;
using TryAtSoftware.CleanTests.Core.Interfaces;
using TryAtSoftware.Extensions.Collections;

Expand All @@ -18,9 +17,12 @@ public CombinatorialMachine(ICleanTestInitializationCollection<ICleanUtilityDesc

public IEnumerable<IDictionary<string, string>> GenerateAllCombinations()
{
var categories = this._utilities.Categories.ToArray();
var (incompatibleUtilitiesMap, incompatibilityEnforcersByCategory, incompatibilityFactorByCategory) = this.DiscoverIncompatibleUtilities();
var categories = this._utilities.Categories.OrderByDescending(x => incompatibilityEnforcersByCategory[x]).ThenByDescending(x => incompatibilityFactorByCategory[x]).ToArray();

Dictionary<string, Dictionary<string, int>> activeDemands = new ();
Dictionary<string, int> incompatibilityFactorByUtilityId = new ();
foreach (var utility in this._utilities.GetAllValues()) incompatibilityFactorByUtilityId[utility.Id] = 0;

Dictionary<string, ICleanUtilityDescriptor> slots = new ();
List<IDictionary<string, string>> resultBag = new ();

Expand All @@ -37,60 +39,69 @@ void Dfs(int categoryIndex)

var currentCategory = categories[categoryIndex];

var requiredCharacteristics = activeDemands.EnsureValue(currentCategory).Keys.ToArray();
foreach (var utility in this._utilities.Get(currentCategory))
{
var canBeUsed = utility.FulfillsAllDemands(requiredCharacteristics);
if (!canBeUsed || !RegisterExternalDemands(utility, slots, activeDemands)) continue;
if (incompatibilityFactorByUtilityId[utility.Id] > 0) continue;

foreach (var iu in incompatibleUtilitiesMap[utility.Id]) incompatibilityFactorByUtilityId[iu]++;
TonyTroeff marked this conversation as resolved.
Show resolved Hide resolved

slots[currentCategory] = utility;
Dfs(categoryIndex + 1);
slots.Remove(currentCategory);

CleanupExternalDemands(utility, activeDemands);
foreach (var iu in incompatibleUtilitiesMap[utility.Id]) incompatibilityFactorByUtilityId[iu]--;
}
}
}

private static bool RegisterExternalDemands(ICleanUtilityDescriptor cleanUtilityDescriptor, IDictionary<string, ICleanUtilityDescriptor> slots, IDictionary<string, Dictionary<string, int>> activeDemands)
private (Dictionary<string, HashSet<string>> IncompatibleUtilitiesMap, Dictionary<string, int> IncompatibilityEnforcersByCategory, Dictionary<string, int> incompatibilityFactorByCategory) DiscoverIncompatibleUtilities()
{
HashSet<string> checkedCategories = new ();
foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands)
Dictionary<string, int> incompatibilityEnforcersByCategory = new (), incompatibilityFactorByCategory = new ();
Dictionary<string, Dictionary<string, HashSet<string>>> characteristicsRegister = new ();
foreach (var category in this._utilities.Categories)
{
if (!slots.TryGetValue(category, out var otherUtilityToCheck)) continue;

if (!otherUtilityToCheck.FulfillsAllDemands(demands)) return false;
checkedCategories.Add(category);
incompatibilityEnforcersByCategory[category] = 0;
incompatibilityFactorByCategory[category] = 0;
characteristicsRegister[category] = new Dictionary<string, HashSet<string>>();
}

foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands)
Dictionary<string, HashSet<string>> incompatibleUtilitiesMap = new ();
foreach (var utility in this._utilities.GetAllValues())
{
if (checkedCategories.Contains(category)) continue;

var activeDemandsForCategory = activeDemands.EnsureValue(category);
foreach (var demand in demands)
incompatibleUtilitiesMap[utility.Id] = new HashSet<string>();

foreach (var characteristic in utility.Characteristics)
{
if (!activeDemandsForCategory.ContainsKey(demand)) activeDemandsForCategory[demand] = 0;
activeDemandsForCategory[demand]++;
if (!characteristicsRegister[utility.Category].ContainsKey(characteristic)) characteristicsRegister[utility.Category][characteristic] = new HashSet<string>();
characteristicsRegister[utility.Category][characteristic].Add(utility.Id);
}
}

return true;
}

private static void CleanupExternalDemands(ICleanUtilityDescriptor cleanUtilityDescriptor, IDictionary<string, Dictionary<string, int>> activeDemands)
{
foreach (var (category, demands) in cleanUtilityDescriptor.ExternalDemands)
foreach (var utility in this._utilities.GetAllValues())
{
if (!activeDemands.TryGetValue(category, out var activeDemandsForCategory)) continue;

foreach (var demand in demands)
foreach (var (demandCategory, demandsForCategory) in utility.ExternalDemands)
{
if (!activeDemandsForCategory.TryGetValue(demand, out var demandOccurrences)) continue;

if (demandOccurrences == 1) activeDemandsForCategory.Remove(demand);
else activeDemandsForCategory[demand] = demandOccurrences - 1;
if (!this._utilities.ContainsCategory(demandCategory)) continue;

foreach (var demand in demandsForCategory)
{
Func<ICleanUtilityDescriptor, bool>? predicate = null;
if (characteristicsRegister[demandCategory].ContainsKey(demand)) predicate = x => !characteristicsRegister[demandCategory][demand].Contains(x.Id);

foreach (var incompatibleUtilityId in this._utilities.Get(demandCategory).SafeWhere(predicate).Select(x => x.Id))
{
if (incompatibleUtilitiesMap[utility.Id].Add(incompatibleUtilityId))
{
incompatibilityEnforcersByCategory[utility.Category]++;
incompatibilityFactorByCategory[utility.Category]++;
}

if (incompatibleUtilitiesMap[incompatibleUtilityId].Add(utility.Id)) incompatibilityFactorByCategory[demandCategory]++;
}
}
}
}

return (incompatibleUtilitiesMap, incompatibilityEnforcersByCategory, incompatibilityFactorByCategory);
}
}