/// <summary> /// Initializes a new instance of the <see cref="ProblemGeneratorOutput"/> class. /// </summary> /// <param name="configuration">The generated configuration.</param> /// <param name="contextualPicture">The contextual picture where the configuration is drawn.</param> /// <param name="oldTheorems">The found theorems for the configurations that don't use the last object of the configuration.</param> /// <param name="newTheorems">The found theorems for the configurations that use the last object of the configuration.</param> public ProblemGeneratorOutput(GeneratedConfiguration configuration, ContextualPicture contextualPicture, TheoremMap oldTheorems, TheoremMap newTheorems) { Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); ContextualPicture = contextualPicture ?? throw new ArgumentNullException(nameof(contextualPicture)); OldTheorems = oldTheorems ?? throw new ArgumentNullException(nameof(oldTheorems)); NewTheorems = newTheorems ?? throw new ArgumentNullException(nameof(newTheorems)); }
/// <inheritdoc/> public TheoremMap FindNewTheorems(ContextualPicture contextualPicture, TheoremMap oldTheorems, out Theorem[] invalidOldTheorems) { // Set no invalid theorems invalidOldTheorems = Array.Empty <Theorem>(); // Return an empty theorem map return(new TheoremMap()); }
/// <inheritdoc/> public RankedTheorem Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems) { // Prepare the ranking dictionary by applying every ranker var rankings = _rankers.Select(ranker => (ranker.RankedAspect, ranking: ranker.Rank(theorem, configuration, allTheorems))) // And wrapping the result to a dictionary together with the coefficient from the settings .ToDictionary(pair => pair.RankedAspect, pair => new RankingData(pair.ranking, _settings.RankingCoefficients[pair.RankedAspect])); // Wrap the final ranking in a ranking object var ranking = new TheoremRanking(rankings); // Now we can return the ranked theorem return(new RankedTheorem(theorem, ranking, configuration)); }
/// <inheritdoc/> public TheoremMap FindNewTheorems(ContextualPicture contextualPicture, TheoremMap oldTheorems, out Theorem[] invalidOldTheorems) { // Find invalid theorems first by taking the old theorems invalidOldTheorems = oldTheorems // For each [type, theorems] pair .SelectMany(pair => { // Find the right theorem finder var finder = _finders.First(finder => finder.Type == pair.Key); // And for every theorem answer the question whether it is no longer valid return(pair.Value.Where(oldTheorem => !finder.ValidateOldTheorem(contextualPicture, oldTheorem))); }) // Enumerate .ToArray(); // Reuse all the finders to find the theorems that are geometrically new in the configuration var uniqueNewTheorems = _finders.SelectMany(finder => finder.FindNewTheorems(contextualPicture)); // We still need to find the new theorems that are not new, due to geometric properties // such as collinearity or concyclity, but can now be stated using the new object // For that we are going to take every old theorem var redefinedNewTheorems = oldTheorems.AllObjects // That is valid .Except(invalidOldTheorems) // And return possibly new versions of it .SelectMany(theorem => // If we have an incidence, we don't want to do anything // (it's not needed to state that A lies on line AB) theorem.Type == TheoremType.Incidence ? Enumerable.Empty <Theorem>() : // Otherwise for each theorem we take its objects theorem.InvolvedObjects // Find the possible definition changes for each // (this includes the option of not changing the definition at all) .Select(theoremObject => FindDefinitionChangeOptions(theoremObject, contextualPicture)) // Combine these definitions in every possible way .Combine() // For every option create a new theorem .Select(objects => new Theorem(theorem.Type, objects))) // We might have gotten even old theorems (when all the definition option changes were // 'no change'. Also we might have gotten duplicates. This call will solve both problems .Except(oldTheorems.AllObjects); // Concatenate these two types of theorems and wrap the result in a map (which will enumerate it) return(new TheoremMap(uniqueNewTheorems.Concat(redefinedNewTheorems))); }
/// <inheritdoc/> public override double Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems) { // Find the number of possible symmetry mappings of loose objects var allSymmetryMappingsCount = configuration.LooseObjectsHolder.GetSymmetricMappings().Count(); // If there is no symmetry mapping for the layout, then 0 seems // like a good ranking if (allSymmetryMappingsCount == 0) { return(0); } // Otherwise find the number of actual symmetry mappings that // preserve both configuration and theorem var validSymmetryMappingsCount = theorem.GetSymmetryMappings(configuration).Count(); // The symmetry ranking is the percentage of valid mappings return((double)validSymmetryMappingsCount / allSymmetryMappingsCount); }
/// <inheritdoc/> public override double Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems) { // Pull the number of constructed objects for comfort var n = configuration.ConstructedObjects.Count; // If there is at most 1 constructed object, let the level be 1 // This should practically not happen, because we are supposed to be ranking // actual interesting problems... if (n <= 1) { return(1); } // Otherwise calculate the levels of objects var levels = configuration.CalculateObjectLevels(); // And apply the formula from the documentations, i.e. 1 - 6[(l1^2+...+ln^2) - n] / [n(n-1)(2n+5)] return(1 - 6d * (levels.Values.Select(level => level * level).Sum() - n) / (n * (n - 1) * (2 * n + 5))); }
/// <inheritdoc/> public override double Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems) // Simply return the number of theorems, excluding the one we cannot prove => allTheorems.AllObjects.Count - 1;
/// <inheritdoc/> public abstract double Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems);
/// <summary> /// Converts given initial theorems to a string. /// </summary> /// <param name="initialFormatter">The formatter of the initial configuration.</param> /// <param name="initialTheorems">The theorems found in the initial configuration.</param> /// <returns>The string representing the theorems.</returns> private static string InitialTheoremsToString(OutputFormatter initialFormatter, TheoremMap initialTheorems) // Process every theorem => initialTheorems.AllObjects // Use formatter for each .Select(initialFormatter.FormatTheorem) // Sort them alphabetically .Ordered() // Append an index to each .Select((theoremString, index) => $" {index + 1,2}. {theoremString}") // Make each on a separate line .ToJoinedString("\n");
/// <summary> /// Performs the analysis of a given generator output. /// </summary> /// <param name="output">The generator output to be analyzed.</param> /// <param name="mode">Indicates how we handle asymmetric problems with regards to generation.</param> /// <param name="constructProofs">Indicates whether we should construct proofs or not, which affects the type of result.</param> /// <returns>The result depending on whether we're constructing proofs or not.</returns> private dynamic Analyze(ProblemGeneratorOutput output, SymmetryGenerationMode mode, bool constructProofs) { // Call the prover var proverOutput = constructProofs // If we should construct proofs, do so ? (object)_prover.ProveTheoremsAndConstructProofs(output.OldTheorems, output.NewTheorems, output.ContextualPicture) // If we shouldn't construct proofs, don't do it : _prover.ProveTheorems(output.OldTheorems, output.NewTheorems, output.ContextualPicture); // Find the proved theorems var provedTheorems = constructProofs // If we have constructed proofs, there is a dictionary ? (IReadOnlyCollection <Theorem>)((IReadOnlyDictionary <Theorem, TheoremProof>)proverOutput).Keys // Otherwise there is a collection directly : (IReadOnlyCollection <Theorem>)proverOutput; // Get the unproven theorems by taking all the new theorems var interestingTheorems = output.NewTheorems.AllObjects // Excluding those that are proven .Where(theorem => !provedTheorems.Contains(theorem)) // Enumerate .ToArray(); // Find the problems that should excluded based on symmetry var notInterestingTheorems = mode switch { // No restrictions SymmetryGenerationMode.GenerateBothSymmetricAndAsymmetric => (IReadOnlyList <Theorem>)Array.Empty <Theorem>(), // Detect symmetric theorems SymmetryGenerationMode.GenerateOnlySymmetric => interestingTheorems.Where(theorem => !theorem.IsSymmetric(output.Configuration)).ToArray(), // Detect fully symmetric theorems SymmetryGenerationMode.GenerateOnlyFullySymmetric => interestingTheorems.Where(theorem => !theorem.IsFullySymmetric(output.Configuration)).ToArray(), // Unhandled cases _ => throw new GeoGenException($"Unhandled value of {nameof(SymmetryGenerationMode)}: {mode}"), }; // Interesting theorems can now be reseted interestingTheorems = interestingTheorems // By the exclusion of not interesting asymmetric ones .Except(notInterestingTheorems) // Enumerate .ToArray(); // Prepare the map of all theorems var allTheorems = new TheoremMap(output.OldTheorems.AllObjects.Concat(output.NewTheorems.AllObjects)); // Rank the interesting theorems var rankedInterestingTheorems = interestingTheorems // Rank given one .Select(theorem => _ranker.Rank(theorem, output.Configuration, allTheorems)) // By rankings ASC (that's why -) .OrderBy(rankedTheorem => - rankedTheorem.Ranking.TotalRanking) // Enumerate .ToArray(); // Now we can finally return the result return(constructProofs // If we're constructing proofs, then we have a proof dictionary ? new GeneratedProblemAnalyzerOutputWithProofs(rankedInterestingTheorems, notInterestingTheorems, (IReadOnlyDictionary <Theorem, TheoremProof>)proverOutput) // If we're not constructing proofs, then we have just a proved theorem collection : (GeneratedProblemAnalyzerOutputBase) new GeneratedProblemAnalyzerOutputWithoutProofs(rankedInterestingTheorems, notInterestingTheorems, (IReadOnlyCollection <Theorem>)proverOutput)); }
/// <summary> /// Proves given theorems that are true in the configuration drawn in a given picture. /// </summary> /// <param name="provenTheorems">The theorems that hold in the configuration without the last object.</param> /// <param name="theoremsToProve">The theorems that say something about the last object.</param> /// <param name="picture">The picture where the configuration in which the theorems hold is drawn.</param> /// <param name="shouldWeConstructProofs">Indicates whether we should construct proofs. This will affect the type of returned result.</param> /// <returns> /// Either the output as for <see cref="ProveTheoremsAndConstructProofs(TheoremMap, TheoremMap, ContextualPicture)"/>, /// if we are constructing proof, or the output as for <see cref="ProveTheorems(TheoremMap, TheoremMap, ContextualPicture)"/> otherwise. /// </returns> private dynamic ProveTheorems(TheoremMap provenTheorems, TheoremMap theoremsToProve, ContextualPicture picture, bool shouldWeConstructProofs) { // Pull the configuration for comfort var configuration = picture.Pictures.Configuration; // Find the trivial theorems based on whether we should do it only // for the last object of the configuration var trivialTheorems = _settings.FindTrivialTheoremsOnlyForLastObject // If yes, do so ? _producer.InferTrivialTheoremsFromObject(configuration.ConstructedObjects.Last()) // Otherwise do it for all objects : configuration.ConstructedObjects.SelectMany(_producer.InferTrivialTheoremsFromObject) // And enumerate the results .ToList(); #region Proof builder initialization // Prepare a proof builder in case we are supposed to construct proofs var proofBuilder = shouldWeConstructProofs ? new TheoremProofBuilder() : null; // Mark trivial theorems to the proof builder in case we are supposed to construct proofs trivialTheorems.ForEach(theorem => proofBuilder?.AddImplication(TrivialTheorem, theorem, assumptions: Array.Empty <Theorem>())); // Mark assumed theorems to the proof builder in case we are supposed to construct proofs provenTheorems.AllObjects.ForEach(theorem => proofBuilder?.AddImplication(AssumedProven, theorem, assumptions: Array.Empty <Theorem>())); #endregion #region Theorems definable simpler // Prepare the list of theorems definable simpler var theoremsDefinableSimpler = new List <Theorem>(); // If we are supposed to assuming them proven... if (_settings.AssumeThatSimplifiableTheoremsAreTrue) { // Go through unproven theorems except for trivial ones foreach (var theoremToProve in theoremsToProve.AllObjects.Except(trivialTheorems)) { // Find the redundant objects var redundantObjects = theoremToProve.GetUnnecessaryObjects(configuration); // If there are none, then it cannot be defined simpler if (redundantObjects.IsEmpty()) { continue; } // Otherwise add it to the list theoremsDefinableSimpler.Add(theoremToProve); // And make sure the proof builder knows it proofBuilder?.AddImplication(new DefinableSimplerInferenceData(redundantObjects), theoremToProve, assumptions: Array.Empty <Theorem>()); } } #endregion #region Theorems inferable from symmetry // Prepare the set of theorems inferred from symmetry var theoremsInferredFromSymmetry = new List <Theorem>(); // Go throw the theorems that are already inferred, i.e. assumed proven, trivial and definable simpler ones foreach (var provedTheorem in provenTheorems.AllObjects.Concat(trivialTheorems).Concat(theoremsDefinableSimpler)) { // Try to infer new theorems using this one foreach (var inferredTheorem in provedTheorem.InferTheoremsFromSymmetry(configuration)) { // Add it to the list theoremsInferredFromSymmetry.Add(inferredTheorem); // Make sure the proof builds knows it proofBuilder?.AddImplication(InferableFromSymmetry, inferredTheorem, assumptions: new[] { provedTheorem }); } } #endregion #region Normalization helper initialization // Initially we are going to assume that the proved theorems are the passed ones var provedTheorems = provenTheorems.AllObjects // And trivial ones .Concat(trivialTheorems) // And ones with redundant objects .Concat(theoremsDefinableSimpler) // And ones inferred from symmetry .Concat(theoremsInferredFromSymmetry) // Distinct ones .Distinct(); // The theorems to prove will be the new ones except for the proved ones var currentTheoremsToBeProven = theoremsToProve.AllObjects.Except(provedTheorems).ToArray(); // Prepare the cloned pictures that will be used to numerically verify new theorems var clonedPictures = picture.Pictures.Clone(); // Prepare a normalization helper with all this information var normalizationHelper = new NormalizationHelper(_verifier, clonedPictures, provedTheorems, currentTheoremsToBeProven); #endregion #region Scheduler initialization // Prepare a scheduler var scheduler = new Scheduler(_manager); // Do the initial scheduling scheduler.PerformInitialScheduling(currentTheoremsToBeProven, configuration); #endregion // Prepare the object introduction helper var objectIntroductionHelper = new ObjectIntroductionHelper(_introducer, normalizationHelper); #region Inference loop // Do until break while (true) { // Ask the scheduler for the next inference data to be used var data = scheduler.NextScheduledData(); #region Object introduction // If there is no data, we will try to introduce objects if (data == null) { // Call the introduction helper var(removedObjects, introducedObject) = objectIntroductionHelper.IntroduceObject( // With the theorems to prove obtained by excluding the proved ones theoremsToProve: theoremsToProve.AllObjects.Except(normalizationHelper.ProvedTheorems)); // Invalidate removed objects removedObjects.ForEach(scheduler.InvalidateObject); // If there is something to be introduced if (introducedObject != null) { // Call the appropriate method to handle introduced objects HandleNewObject(introducedObject, normalizationHelper, scheduler, proofBuilder); // Ask the scheduler for the next inference data to be used data = scheduler.NextScheduledData(); } } #endregion // If all theorems are proven or there is no data even after object introduction, we're done if (!normalizationHelper.AnythingLeftToProve || data == null) { // If we should construct proofs return(shouldWeConstructProofs // Build them for the theorems to be proven ? (dynamic)proofBuilder.BuildProofs(theoremsToProve.AllObjects) // Otherwise just take the theorems to be proven that happen to be proven : theoremsToProve.AllObjects.Where(normalizationHelper.ProvedTheorems.Contains).ToReadOnlyHashSet()); } #region Inference rule applier call // Try to apply the current scheduled data var applierResults = _applier.InferTheorems(new InferenceRuleApplierInput ( // Pass the data provided by the scheduler inferenceRule: data.InferenceRule, premappedAssumption: data.PremappedAssumption, premappedConclusion: data.PremappedConclusion, premappedObject: data.PremappedObject, // Pass the methods that the normalization helper offers mappableTheoremsFactory: normalizationHelper.GetProvedTheoremOfType, mappableObjectsFactory: normalizationHelper.GetObjectsWithConstruction, equalObjectsFactory: normalizationHelper.GetEqualObjects, normalizationFunction: normalizationHelper.GetNormalVersionOfObjectOrNull )) // Enumerate results. This step is needed because the applier could iterate over the // collections of objects and theorems used by the normalization helper .ToArray(); // Before handling results prepare a variable that will indicate whether there has been any change of the // normal version of an object. var anyNormalVersionChange = false; // Handle every inferred theorems foreach (var(theorem, negativeAssumptions, possitiveAssumptions) in applierResults) { // If in some previous iteration there has been a change of the normal version of an object, then // it might happen that some other theorems inferred in this loop no longer contain only correct objects, // therefore we need to verify them. The reason why we don't have to worry about incorrect objects in other // cases is that the normalization helper keeps everything normalized and the inference rule applier provides // only normalized objects. However, if there is a change of normal versions and the applier is already called // and the results are enumerated, then we have to check it manually if (anyNormalVersionChange && normalizationHelper.DoesTheoremContainAnyIncorrectObject(theorem)) { continue; } // We need to check negative assumptions. The inference should be accepted only if all of them are false if (negativeAssumptions.Any(negativeAssumption => _verifier.IsTrueInAllPictures(clonedPictures, negativeAssumption))) { continue; } // Prepare the proof data in case we need to construct proofs var proofData = shouldWeConstructProofs ? new ProofData(proofBuilder, new CustomInferenceData(data.InferenceRule), possitiveAssumptions) : null; // Prepare the variable indicating whether the theorem is geometrically valid bool isValid; // If this is an equality if (theorem.Type == EqualObjects) { // Call the appropriate method to handle it while finding out whether there has been any normal version change HandleEquality(theorem, normalizationHelper, scheduler, proofData, out isValid, out var anyNormalVersionChangeInThisIteration); // If yes, then we set the outer loop variable indicating the same thing for the whole loop if (anyNormalVersionChangeInThisIteration) { anyNormalVersionChange = true; } } // If this is a non-equality else { // Call the appropriate method to handle it HandleNonequality(theorem, normalizationHelper, scheduler, proofData, out isValid); } // If the theorem turns out not to be geometrically valid, trace it if (!isValid) { _tracer.MarkInvalidInferrence(configuration, theorem, data.InferenceRule, negativeAssumptions, possitiveAssumptions); } } #endregion } #endregion }
/// <inheritdoc/> public IReadOnlyDictionary <Theorem, TheoremProof> ProveTheoremsAndConstructProofs(TheoremMap provenTheorems, TheoremMap theoremsToProve, ContextualPicture picture) // Delegate the call to the general method and cast the result => ProveTheorems(provenTheorems, theoremsToProve, picture, shouldWeConstructProofs: true);
/// <inheritdoc/> public IReadOnlyHashSet <Theorem> ProveTheorems(TheoremMap provenTheorems, TheoremMap theoremsToProve, ContextualPicture picture) // Delegate the call to the general method and cast the result => ProveTheorems(provenTheorems, theoremsToProve, picture, shouldWeConstructProofs: false);
/// <inheritdoc/> public override double Rank(Theorem theorem, Configuration configuration, TheoremMap allTheorems) // Simply return the number of theorems of type ConcyclicPoints => allTheorems.GetObjectsForKeys(TheoremType.ConcyclicPoints).Count() // Potentially excluding the one we cannot prove, if it's of type ConcyclicPoints - (theorem.Type == TheoremType.ConcyclicPoints ? 1 : 0);