// 5 members with 3 relations between them // member 1 has relations to 2 & 3 // member 4 has a relation to member 3, member 5 has no relations public static SetExpectation GetMediumLargeSetCase(GroupRatingConfiguration ratingConfig) { var relationRatings = new RelationRatingsData().Initialize(3); relationRatings[0] = SimpleRelationRatings1; relationRatings[1] = SimpleRelationRatings2; relationRatings[2] = SimpleRelationRatings3; var memberContribution = (0.8f + 0.9f + 0.72f + 0.5f + 0.3f) / 5f; var relationContribution = Mathf.Pow(0.75f * 0.6f * 0.4f, 1f / 3f); return(new SetExpectation() { RatingConfiguration = ratingConfig, // since we only have one relation, rated 0.75, it contributes all the relation ratings //ApproximateExpectedRating = (memberContribution + relationContribution) * 0.5f, ApproximateExpectedRating = GetExpected(memberContribution, relationContribution, ratingConfig), Members = new[] { new KeyValuePair <int, float>(1, 0.8f), new KeyValuePair <int, float>(4, 0.9f), new KeyValuePair <int, float>(8, 0.72f), new KeyValuePair <int, float>(3, 0.5f), new KeyValuePair <int, float>(10, 0.3f), }, RelationRatings = relationRatings, LocalRelationIndices = new[] { new RelationDataPair(0, 1), new RelationDataPair(0, 2), new RelationDataPair(3, 2), } }); }
public static bool TryMatchAllInternal(Relations relations, RelationTraitCache traits, RelationRatingsData ratings, Dictionary <int, float>[] memberRatings, RelationDataPair[] relationPairs) { if (relations.GetTypeCount(out IRelation <System.Int32>[] intRelations) > 0) { if (!traits.TryGetType(out List <RelationTraitCache.ChildTraits <System.Int32> > intTraits) || !RateMatches(intRelations, intTraits, relationPairs, memberRatings, ratings)) { return(false); } } if (relations.GetTypeCount(out IRelation <UnityEngine.Pose>[] poseRelations) > 0) { if (!traits.TryGetType(out List <RelationTraitCache.ChildTraits <UnityEngine.Pose> > poseTraits) || !RateMatches(poseRelations, poseTraits, relationPairs, memberRatings, ratings)) { return(false); } } if (relations.GetTypeCount(out IRelation <UnityEngine.Vector2>[] vector2Relations) > 0) { if (!traits.TryGetType(out List <RelationTraitCache.ChildTraits <UnityEngine.Vector2> > vector2Traits) || !RateMatches(vector2Relations, vector2Traits, relationPairs, memberRatings, ratings)) { return(false); } } return(true); }
/// <summary> /// Try to find matches for all Relations in a Set. /// </summary> /// <param name="relations">All relations in the set</param> /// <param name="traits">References to the trait values used by all relations</param> /// <param name="ratingData">Output list of dictionaries</param> /// <param name="memberRatings">The collection of all member ratings</param> /// <param name="relationPairs">The relation index pairs for this Set</param> /// <returns>True if all relations found at least 1 match, false otherwise</returns> public static bool TryMatchAll(Relations relations, RelationTraitCache traits, RelationRatingsData ratingData, Dictionary <int, float>[] memberRatings, RelationDataPair[] relationPairs) { CurrentRelationPairIndex = 0; foreach (var dictionary in ratingData) { dictionary.Clear(); } return(TryMatchAllInternal(relations, traits, ratingData, memberRatings, relationPairs)); }
// this version should fail, because the member assignments do not form a valid Relation public static SetExpectation GetSimpleFailingSetCase() { var relationRatings = new RelationRatingsData().Initialize(1); relationRatings[0] = SimpleRelationRatings1; return(new SetExpectation() { Members = new[] { new KeyValuePair <int, float>(4, 0.8f), new KeyValuePair <int, float>(2, 0.9f), }, RelationRatings = relationRatings, LocalRelationIndices = new[] { new RelationDataPair(0, 1) } }); }
// A very simple Set - 2 members with one Relation between them. public static SetExpectation GetSimpleSetCase(GroupRatingConfiguration ratingConfig) { var relationRatings = new RelationRatingsData().Initialize(1); relationRatings[0] = SimpleRelationRatings1; return(new SetExpectation() { RatingConfiguration = ratingConfig, // since we only have one relation, rated 0.75, it contributes all the relation ratings //ApproximateExpectedRating = ((0.8f + 0.9f) * 0.5f + 0.75f) * 0.5f, ApproximateExpectedRating = GetExpected((0.8f + 0.9f) * 0.5f, 0.75f, ratingConfig), Members = new[] { new KeyValuePair <int, float>(1, 0.8f), new KeyValuePair <int, float>(4, 0.9f), }, RelationRatings = relationRatings, LocalRelationIndices = new[] { new RelationDataPair(0, 1) } }); }
public void TryMatchAll() { var dataSet = new RelationRatingTestData.SingleMatchUsingSameTrait(); var testObject = m_SetQueryTestObject; var relations = TestUtils.GetRelations(testObject); var traitCache = new RelationTraitCache(relations); Assert.True(traitCache.TryGetType(out List <RelationTraitCache.ChildTraits <float> > floatTraits)); traitCache.TryGetType(out List <RelationTraitCache.ChildTraits <Pose> > poseTraits); floatTraits[0] = new RelationTraitCache.ChildTraits <float>(dataSet.FloatTraitValues); // start with one of our trait values being empty of data, so we can test a failure case poseTraits[0] = new RelationTraitCache.ChildTraits <Pose>(new Dictionary <int, Pose>()); var relationRatings = new RelationRatingsData(relations); var memberRatingsArray = RandomRatingsArray(relations.children.Count); var relationIndexPairs = new RelationDataPair[relations.Count]; for (var i = 0; i < relations.Count; i++) { // for the purposes of this test, we just want to see that the lower-level // abstraction that works on a per-type basis works for all types, // so it doesn't matter if the index pair data is accurate relationIndexPairs[i] = new RelationDataPair(i, i == relations.Count - 1 ? 0 : i + 1); } // because our pose relation has no trait data, it should fail and cause the whole result to be false Assert.False(RelationRatingTransform.TryMatchAll(relations, traitCache, relationRatings, memberRatingsArray, relationIndexPairs)); // assign usable data to the trait that had no values before, and then re-try - now it should succeed poseTraits[0] = new RelationTraitCache.ChildTraits <Pose>(dataSet.PoseTraitValues); Assert.True(RelationRatingTransform.TryMatchAll(relations, traitCache, relationRatings, memberRatingsArray, relationIndexPairs)); // The default set test prefab has a float and pose relation on it, so we expect 2 Assert.AreEqual(2, relationRatings.Count); VerifyRelationRatings(relationRatings[0], floatTraits[0]); VerifyRelationRatings(relationRatings[1], poseTraits[0]); }
/// <summary> /// For a Set with any Relations, iterate over a portion of possible combinations of member assignments. /// For each combination that satisfies all Relations, reduce Relation & member ratings into a single rating. /// </summary> /// <param name="matchBuffer">A buffer to record valid combinations with their ratings in</param> /// <param name="relationRatings">The Relation ratings for this single Set</param> /// <param name="localRelationPairs">The local relation index pairs for this Set</param> /// <returns></returns> bool TryCombinations(SetMatchesBuffer matchBuffer, RelationRatingsData relationRatings, RelationDataPair[] localRelationPairs) { var config = RatingConfiguration; matchBuffer.Reset(); do { var hypothesis = m_MetaEnumerator.Current; // determine if a given combination of member assignments is valid according to our Relations if (!TryRateAssignmentSet(hypothesis, relationRatings, localRelationPairs, config, out var rating)) { continue; } matchBuffer.Add(hypothesis, rating); } // this call produces the next combination to rate, until we reach our iteration target while (m_MetaEnumerator.MoveNext()); // for Sets with Relations, we filter by availability and duplicates only after picking a match return(matchBuffer.Count > 0); }
/// <summary> /// Verify that a given assignment hypothesis satisfies all Relations, and produce a rating for it if so. /// This function assumes that the Set has at least one Relation. /// For sets without relations, AverageMemberRating should be used instead. /// </summary> /// <param name="hypothesis">The assignment combination to verify the validity of</param> /// <param name="setRelationsRatings">The relation ratings for the Set</param> /// <param name="localRelationPairs">Pairs that map each relation's members within the local collections</param> /// <param name="ratingConfiguration">The configured weight to use for producing the final ratings</param> /// <param name="reducedRating">The final rating for the combination. 0 if the combination is invalid</param> /// <returns>True if the combination was valid, false otherwise</returns> internal static bool TryRateAssignmentSet(KeyValuePair <int, float>[] hypothesis, RelationRatingsData setRelationsRatings, RelationDataPair[] localRelationPairs, GroupRatingConfiguration ratingConfiguration, out float reducedRating) { // for every Relation, check if its member's assignments are valid within our hypothesis var relationRatings = 1f; for (var i = 0; i < localRelationPairs.Length; i++) { var ratings = setRelationsRatings[i]; var indexPair = localRelationPairs[i]; var child1 = hypothesis[indexPair.Child1].Key; var child2 = hypothesis[indexPair.Child2].Key; // if this assignment pair doesn't satisfy this relation, we know to move onto another assignment combo if (!ratings.TryGetValue(new RelationDataPair(child1, child2), out var pairRating)) { reducedRating = 0f; return(false); } relationRatings *= pairRating; } // combine our relation ratings just like our condition ones - the "inverse power" method var reducedRelationRating = Mathf.Pow(relationRatings, 1f / localRelationPairs.Length); // combine the relation & member ratings according to the configured weights var relationsWeighted = reducedRelationRating * (1f - ratingConfiguration.MemberMatchWeight); var membersWeighted = AverageMemberRating(hypothesis) * ratingConfiguration.MemberMatchWeight; reducedRating = relationsWeighted + membersWeighted; return(true); }
// for each Relation this member belongs to, if a data ID from member matches isn't found in that // relation's matches, remove that ID from the member's match ratings, because it can't work. internal void RemoveInvalidMemberMatches(RelationMembership[] memberships, RelationRatingsData allRelationRatings, Dictionary <int, float> memberRatings) { var memberKeys = memberRatings.Keys; foreach (var membership in memberships) { var removalBufferCounter = 0; var singleRelationRatings = allRelationRatings[membership.RelationIndex].Keys; // the only difference between these branches is which relation child key we check against. // it's organized this way so that this if check is moved out of the two inner loops. if (membership.FirstMember) { foreach (var memberKey in memberKeys) { var memberKeyFound = false; foreach (var relationPair in singleRelationRatings) { if (relationPair.Child1 == memberKey) { memberKeyFound = true; break; } } if (memberKeyFound) { continue; } m_IdRemovalBuffer[removalBufferCounter] = memberKey; removalBufferCounter++; } } else { foreach (var memberKey in memberKeys) { var memberKeyFound = false; foreach (var relationPair in singleRelationRatings) { if (relationPair.Child2 == memberKey) { memberKeyFound = true; break; } } if (memberKeyFound) { continue; } m_IdRemovalBuffer[removalBufferCounter] = memberKey; removalBufferCounter++; } } for (var i = 0; i < removalBufferCounter; i++) { memberRatings.Remove(m_IdRemovalBuffer[i]); } } }