/// <summary> /// Checks whether the given <see cref="MathProgram" /> computes a value that is consistently equivalent to that /// computed /// by another program according to some margin. The method works by substituting the variables in both programs' /// expressions by random values for a certain number of trials. In each trial, the difference between the values /// computed by both programs is calculated. If the difference is less than a given margin for all the trials, then the /// programs are considered to be value-equivalent. /// </summary> /// <param name="program">The first program that we want to test.</param> /// <param name="other">The second program that we want to test.</param> /// <param name="margin"> /// The margin used to compare against the difference of values computed by both expressions in each /// trial. /// </param> /// <param name="numTrials">The number of trials used to discern whether the given programs are equivalent.</param> /// <returns><c>True</c> if the given programs are considered to be value-equivalent, <c>False</c> otherwise.</returns> public static bool IsValueEquivalent( this ITreeProgram <double> program, ITreeProgram <double> other, double margin = DEFAULT_MARGIN, uint numTrials = DEFAULT_NUM_TRIALS) { // checks null if (program == null || other == null) { return(false); } // checks equivalence if (program.Equals(other)) { return(true); } // checks expression or constant equivalence after simplification program = program.Simplify(); other = other.Simplify(); if (program.Equals(other) || program.IsConstant() && other.IsConstant() && Math.Abs(program.Compute() - other.Compute()) < margin) { return(true); } // gets RMSE by replacing the values of the variables in some number of trials return(program.GetValueRmsd(other, numTrials) <= margin); }
/// <summary> /// Gets a new copy of <paramref name="program" /> where all sub-programs that are equal to /// <paramref name="oldSubProgram" /> are replaced by <paramref name="newSubProgram" />. /// </summary> /// <param name="program">The root program to copy and search for the given sub-program .</param> /// <param name="oldSubProgram">The sub-program we want to replace.</param> /// <param name="newSubProgram">The new sub-program to replace the given one.</param> /// <returns> /// A copy of the program with the given descendant replaced by the new program. If the given program is equal to the /// sub-program we want to replace, then the replacement is returned. If the given sub-program is not found, a copy of /// the original program is returned, or <c>null</c> if the program is <c>null</c>. /// </returns> /// <typeparam name="TOutput">The type of program output.</typeparam> public static ITreeProgram <TOutput> Replace <TOutput>( this ITreeProgram <TOutput> program, ITreeProgram <TOutput> oldSubProgram, ITreeProgram <TOutput> newSubProgram) { if (program == null || oldSubProgram == null || newSubProgram == null) { return(program); } // checks if program is equal, return replacement if (program.Equals(oldSubProgram)) { return(newSubProgram); } // replaces children recursively and creates a new program if (program.Input == null || program.Input.Count == 0) { return(program); } var children = new ITreeProgram <TOutput> [program.Input.Count]; for (var i = 0; i < program.Input.Count; i++) { children[i] = Replace(program.Input[i], oldSubProgram, newSubProgram); } return(program.CreateNew(children)); }
/// <summary> /// Gets the root-mean-square deviation (RMSD) between the values computed for the given programs. The method works by /// substituting the variables in both programs' expressions by random values for a certain number of trials. In each /// trial, the square of the difference between the values computed by both programs is calculated. /// </summary> /// <param name="program">The first program that we want to test.</param> /// <param name="other">The second program that we want to test.</param> /// <param name="numTrials">The number of trials used to compute the squared difference.</param> /// <returns>The RMSD between the several values computed for the given programs.</returns> public static double GetValueRmsd( this ITreeProgram <double> program, ITreeProgram <double> other, uint numTrials = DEFAULT_NUM_TRIALS) { // checks null if (program == null || other == null) { return(double.MaxValue); } // checks expression or constant equivalence after simplification program = program.Simplify(); other = other.Simplify(); if (program.Equals(other) || program.IsConstant() && other.IsConstant()) { return(Math.Abs(program.Compute() - other.Compute())); } // replaces variables of each expression by custom variables var customVariables = new Dictionary <Variable, Variable>(); program = ReplaceVariables(program, customVariables); other = ReplaceVariables(other, customVariables); if (customVariables.Count == 0) { return(double.MaxValue); } // gets random values for each variable var customVariablesValues = customVariables.Values.ToDictionary( variable => variable, variable => GetTrialRandomNumbers(numTrials, variable.Range)); // runs a batch of trials var squareDiffSum = 0d; for (var i = 0; i < numTrials; i++) { // replaces the value of each variable by a random number foreach (var customVariablesValue in customVariablesValues) { customVariablesValue.Key.Value = customVariablesValue.Value[i]; } // computes difference of the resulting values of the expressions var prog1Value = program.Compute(); var prog2Value = other.Compute(); var diff = prog1Value.Equals(prog2Value) ? 0 : prog1Value - prog2Value; squareDiffSum += diff * diff; } // returns RMSD return(Math.Sqrt(squareDiffSum / numTrials)); }
/// <summary> /// Checks whether a given <see cref="ITreeProgram{TOutput}" /> is a sub-program of another /// <see cref="ITreeProgram{TOutput}" />. /// A sub-program is an program that is a descendant of a given program. /// </summary> /// <param name="program">The program we want to know if it is a sub-program.</param> /// <param name="other">The program for which to look for a descendant equal to the given program.</param> /// <returns><c>true</c>, if the program is a descendant of the other program, <c>false</c> otherwise.</returns> /// <typeparam name="TOutput">The type of program output.</typeparam> public static bool IsSubProgramOf <TOutput>(this ITreeProgram <TOutput> program, ITreeProgram <TOutput> other) { // checks prog if (program == null || other == null || program.Length >= other.Length || other.Input?.Count == 0) { return(false); } // search the descendants for the given program return(other.Input != null && other.Input.Any(child => program.Equals(child) || program.IsSubProgramOf(child))); }
/// <summary> /// Checks whether a given <see cref="ITreeProgram{TOutput}" /> contains the given sub-program. /// A sub-program is an program that is a descendant of a given program. /// </summary> /// <returns><c>true</c>, if the given program is a descendant of the given program, <c>false</c> otherwise.</returns> /// <param name="program">The program for which to look for a descendant equal to the given program.</param> /// <param name="other">The program we want to know if it is a sub-program.</param> /// <typeparam name="TOutput">The type of program output.</typeparam> public static bool ContainsSubElement <TOutput>( this ITreeProgram <TOutput> program, ITreeProgram <TOutput> other) { // checks prog if (program == null || other == null || program.Length <= other.Length || program.Input?.Count == 0) { return(false); } // search the descendants for the given program return(program.Input != null && program.Input.Any(child => other.Equals(child) || child.ContainsSubElement(other))); }
private static double Calculate(ITreeProgram <TOutput> prog1, ITreeProgram <TOutput> prog2) { // checks simple cases if (prog1 == null || prog2 == null) { return(0); } if (prog1.Equals(prog2)) { return(1); } // replace all common sub-programs by weighted variables var ignorePrograms = new HashSet <ITreeProgram <TOutput> >(); var minCount = Math.Min(prog1.Length, prog2.Length); for (var i = 0; i < minCount; i++) { var largestCommon = GetLargestCommon(ref prog1, ref prog2, ignorePrograms); if (largestCommon == null || largestCommon.Length < 2) { break; } var newSubProgram = new WeightedVariable(largestCommon.Expression, largestCommon.Length); prog1 = prog1.Replace(largestCommon, newSubProgram); prog2 = prog2.Replace(largestCommon, newSubProgram); ignorePrograms.Add(newSubProgram); } // gets sub-programs var subProgs1 = new List <ITreeProgram <TOutput> > { prog1 }; subProgs1.AddRange(prog1.GetSubPrograms()); var subProgs2 = new List <ITreeProgram <TOutput> > { prog2 }; subProgs2.AddRange(prog2.GetSubPrograms()); // tries to align both trees in all possible ways var minRelCost = double.MaxValue; for (var i = 0u; i < prog1.Length; i++) { for (var j = 0u; j < prog2.Length; j++) { // alignment is determined by the starting indexes of both trees var idx1 = i; var idx2 = j; // adds cost of having to add previous nodes (shifting/alignment cost) var cost = i + j; var totalCost = cost; // gets edit cost between sub-programs GetEditCost(subProgs1, subProgs2, ref idx1, ref idx2, ref cost, ref totalCost); // checks cost of having to add remaining nodes var remainder = prog1.Length - idx1 + prog2.Length - idx2 - 2; cost += remainder; totalCost += remainder; var relCost = (double)cost / totalCost; // updates min relative cost if (relCost < minRelCost) { minRelCost = relCost; } } } return(1d - minRelCost); }