هذه المقالة ، إلى حد ما ، استمرار لمقالتي حول تقليل الوظائف المنطقية بطريقة Quine-Mac'Klaski (
https://habr.com/post/328506 ). وقد نظرت في حالة ذات وظائف منطقية محددة تمامًا (على الرغم من أن هذا لم يتم ذكرها مباشرة فيها ، ولكن ضمنيًا فقط). في الواقع ، مثل هذه الحالة نادرة جدًا عندما يكون عدد متغيرات الإدخال صغيرًا. إن التعريفات الجزئية أو غير المكتملة هي وظائف منطقية يتم إعطاء قيمها فقط للجزء Q من المجموعة الكاملة P =
المجموعات (المصطلحات) الممكنة لحججهم (المتغيرات) من العدد
N ، أي Q <P. تمت مواجهة هذا الموقف عمليًا في معظم حالات تطبيق الخوارزميات لتحسين الوظائف المنطقية. في الواقع ، على سبيل المثال ، إذا كان عدد متغيرات المدخلات هو
N = 30 ، وهي حالة عادية ، على سبيل المثال ، في الأسواق المالية ، فيجب أن يكون حجم عينة تدريب المدخلات من ترتيب
>
شروط فريدة فريدة من نوعها. لا توجد مثل هذه المجموعة من البيانات في كل مؤسسة كبيرة جدًا ، ناهيك عن الأفراد ، وهذا هو بالفعل مجال BigData ، واستخدام مراكز البيانات ، إلخ.
لذلك ، في الممارسة العملية ، في الغالب لن يتم تحديد الوظائف المنطقية المصغرة بشكل كامل وذلك ببساطة بسبب نقص الكمية المطلوبة من البيانات المتراكمة أو بسبب أسباب موضوعية أخرى (على سبيل المثال ، لا توجد مساحة كافية لتخزينها). يطرح السؤال حول إمكانية "التحايل" على هذه المشكلة عند استخدام خوارزمية تعمل مع مجموعة محددة تمامًا من الوظائف المنطقية للمصطلح ، مثل ، على سبيل المثال ، من مقالتي السابقة.
تتمثل الممارسة القياسية في هذه الحالة في تحديد مجموعة المدخلات غير المكتملة لقيم (مصطلح) المتغيرة إلى القيمة الكاملة بحيث تعطي نتيجة مثالية لمجموعة البيانات الحالية. ولكن ، في هذه الحالة ، هناك مشكلة في تعداد جميع المتغيرات الممكنة للتعريفات الإضافية ، والتي يبلغ عددها الإجمالي V =
لتحديد الخيار الأفضل لتعريف إضافي وفقًا لمعيار معين. من الواضح ، بالنسبة للقيم المستخدمة بالفعل لـ Q و P ، فإن عدد الخيارات المصنفة لتعريفات إضافية كبير فلكيًا ولا يمكن تنفيذ هذا النهج في الممارسة العملية بسبب التكلفة الحسابية الهائلة.
وبالتالي ، هناك حاجة إلى نهج مختلف من شأنه أن يلغي الحاجة إلى تعداد خيارات مختلفة لتعاريف إضافية. لذلك ، من الضروري تحديث الخوارزمية الأصلية ، التي تعمل في البداية فقط مع مجموعة إدخال محددة بالكامل ، بحيث يمكنها أيضًا العمل مع مجموعة مقطوعة. إنه تطبيق للخوارزمية المقترحة في هذه المقالة ، استنادًا إلى حقيقة أنه أثناء عملية التصغير تتم معالجة قائمتين غير مكتملتين من المصطلحات في وقت واحد ، حيث يتم تحديد الوظيفة على أنها FALSE (0) و TRUE (1).
من وجهة نظر التعلم الآلي ، تطبق خوارزمية Quine-Mac'Klaski نموذج التعلم مع المعلم عندما يتم تضمين قيم المخرجات المقابلة للوظيفة الموضوعية في عملية التعلم (في هذه الحالة ، التقليل) في وقت واحد. دعني أذكرك أن مبدأ تشغيل طريقة Quine-Mac'Klaski الأساسية وفقًا للنظرية يتكون من مرحلتين رئيسيتين:
- المرحلة. إيجاد جميع مصطلحات LF البسيطة باستخدام قواعد (قوانين) اللصق:
أ) (أ و ب)؟ (A &! B)؟ أ ؛
ب) (أ؟ ب) و (أ؟ ب)؟ أ ؛
أين & المنطقية AND العملية ؛؟ - تشغيل المنطقية "OR" ؛! - تشغيل النفي المنطقي "NOT". ويترتب على هذه الصيغ أنه يتم لصق مصطلحين معًا إذا اختلف كل منهما عن الآخر فقط في أحد مواضع المتغيرات. في الموضع الذي يختلف فيه المصطلحان عن بعضهما البعض ، يتم وضع العلامة "*". وبالتالي ، فإن الأبجدية من حيث اللصق مقارنة مع الأصلي يمتد إلى ثلاث قيم:
• 0 => خطأ ؛
• 1 => صحيح ؛
• 2 => متغير لاصق (*). - المرحلة. تقليل عدد المصطلحات الملصوقة التي تم الحصول عليها بعد المرحلة الأولى ، كمشكلة في العثور على التغطية المثلى لمجموعة المصطلحات الأولية بالكمية Q. أي ، نظرًا لأن كل مصطلح ناتج يغطي مجموعة فرعية معينة من مصطلحات المصدر ، فمن الضروري اختيار مجموعة دنيا من مصطلحات الإخراج التي تم تحديدها مع معهم ، غطت مجموعات فرعية بأطوال مختلفة في المجموع جميع شروط الإدخال الأولية. يعني الطلاء في هذه الحالة أن التشغيل أحادي الاتجاه لفصل مصطلح الإخراج على مدى الإدخال يعطي قيمة حقيقية. لنفترض أن مصطلح اللصق الناتج يحتوي على الشكل التالي: 10 * 0110 *.
ثم يغطي المصطلح 10101100:
10 * 0110 * & 10101100 = صحيح
ولكن لا يشمل المصطلح 00101100:
10 * 0110 * & 00101100 = خطأ
أي ، يجب أن يتطابق مصطلح الإدخال والإخراج في كل مكان باستثناء المواضع التي يوجد بها الرمز "*" - في هذا الموضع يمكن لمتغير مصطلح الإدخال أن يأخذ أي قيمة ، لأن في هذا الموقف يتم استبعاد المتغير من الاعتبار.
رمز التنفيذ على النحو التالي (انقر للعرض):using System; using System.Collections.Generic; using System.Linq; #region /// <summary> /// /// </summary> public abstract class LogicFunction { // "" public const byte cStarSymb = 2; // public readonly ICollection<byte[]> Terms = new LinkedList<byte[]>(); // public abstract bool Calculate(bool[] X); // public abstract bool Calculate(char[] X); // public abstract bool Calculate(byte[] X); } /// <summary> /// /// </summary> public class Dnf : LogicFunction { public static bool Calculate(byte[] X, byte[] term) { bool bResult = true; for (int i = 0; i < term.Length; i++) { if ((term[i] == cStarSymb) || (term[i] == X[i])) continue; bResult = false; break; } return bResult; } public override bool Calculate(byte[] X) { bool bResult = false; foreach (byte[] term in Terms) { bool bTermVal = true; for (int i = 0; i < term.Length; i++) { if ((term[i] >= cStarSymb) || (term[i] == X[i])) continue; bTermVal = false; break; } //bResult |= bTermVal; if (bTermVal) { bResult = true; break; } } return bResult; } public override bool Calculate(char[] X) { bool bResult = false; foreach (byte[] term in Terms) { bool bTermVal = true; for (int i = 0; i < term.Length; i++) { if ((term[i] >= cStarSymb) || (term[i] == (byte)(X[i] == '0' ? 0 : 1))) continue; bTermVal = false; break; } //bResult |= bTermVal; if (bTermVal) { bResult = true; break; } } return bResult; } public override bool Calculate(bool[] X) { bool bResult = false; foreach (byte[] term in Terms) { bool bTermVal = true; for (int i = 0; i < term.Length; i++) { if ((term[i] >= cStarSymb) || ((term[i] != 0) == X[i])) continue; bTermVal = false; break; } //bResult |= bTermVal; if (bTermVal) { bResult = true; break; } } return bResult; } } #endregion /// <summary> /// /// </summary> public class TreeFuncTerm { /// <summary> /// /// </summary> public class TreeNodeEnd { } // private readonly TreeNodeEnd pCommonTreeNodeEnd = new TreeNodeEnd(); // private readonly object[] rootNode = new object[3]; // () private int _rang = 0; public int Rang { get { return _rang; } } // private int enumerationPos = 0; private object[][] enumerationBuf; //, private byte[] enumerationTerm; public byte[] EnumerationTerm { get { return enumerationTerm; } } // private UInt32 _count = 0; public UInt32 Count { get { return _count; } } // public TreeFuncTerm() { Clear(); } // public void Clear() { _count = 0; _rang = 0; enumerationPos = 0; enumerationBuf = null; enumerationTerm = null; rootNode[0] = rootNode[1] = rootNode[2] = null; } // public TreeNodeEnd EnumerationInit() { enumerationPos = 0; enumerationTerm = new byte[_rang]; enumerationTerm[0] = 0; enumerationBuf = new object[_rang][]; enumerationBuf[0] = rootNode; // return EnumerationNextNode(); } // public TreeNodeEnd EnumerationNextNode() { int iIsNext = (enumerationPos > 0 ? 1 : 0); TreeNodeEnd pRetTreeNode = null; while ((pRetTreeNode == null) && (enumerationPos >= 0)) { object[] pCurrNodes = enumerationBuf[enumerationPos]; object pNextNode = null; int i = enumerationTerm[enumerationPos] + iIsNext; for (; i < 3; i++) if ((pNextNode = pCurrNodes[i]) != null) break; if (pNextNode == null) { // enumerationPos--; iIsNext = 1; } else { enumerationTerm[enumerationPos] = (byte)i; if (pNextNode is object[]) { // enumerationPos++; enumerationBuf[enumerationPos] = (object[])pNextNode; enumerationTerm[enumerationPos] = 0; iIsNext = 0; } else //if (pNextNode is TreeNodeEnd) { // pRetTreeNode = (TreeNodeEnd)pNextNode; } } } return pRetTreeNode; } // public void AddTerm(byte[] term) { _rang = Math.Max(_rang, term.Length); object[] pCurrNode = rootNode; int iTermLength1 = term.Length - 1; for (int j = 0; j < iTermLength1; j++) { byte cSymb = term[j]; object item = pCurrNode[cSymb]; if (item == null) { item = new object[3]; pCurrNode[cSymb] = item; } pCurrNode = (object[])item; } if (pCurrNode[term[iTermLength1]] == null) { // pCurrNode[term[iTermLength1]] = pCommonTreeNodeEnd; _count++; } } // public TreeNodeEnd Remove(byte[] term) { int iTermLength1 = term.Length - 1; object[] pCurrNode = rootNode; for (int i = 0; i < iTermLength1; i++) { pCurrNode = (object[])pCurrNode[term[i]]; if (pCurrNode == null) break; } TreeNodeEnd pRemovedNode = null; if (pCurrNode != null) { // pRemovedNode = (TreeNodeEnd)pCurrNode[term[iTermLength1]]; if (pRemovedNode != null) { // pCurrNode[term[iTermLength1]] = null; // - _count--; } } return pRemovedNode; } // public bool Contains(byte[] term) { object pCurrNode = rootNode; foreach (byte cSymb in term) { pCurrNode = ((object[])pCurrNode)[cSymb]; if (pCurrNode == null) break; } return ((pCurrNode != null) && (pCurrNode is TreeNodeEnd)); } } /// <summary> /// ---- /// </summary> public class Quine_McCluskey { // private readonly Dnf _result = new Dnf(); public Dnf Result { get { return _result; } } // private readonly Dnf _resultNeg = new Dnf(); public Dnf ResultNeg { get { return _resultNeg; } } // private static void Skleivanie(TreeFuncTerm X1Tree, TreeFuncTerm X2Tree, TreeFuncTerm NegativTree, IEnumerable<byte[]> InpNegTerms, Dictionary<int, LinkedList<byte[]>> OutResult, int iLevel) { LinkedList<byte[]> OutR = new LinkedList<byte[]>(); if (OutResult != null) OutResult.Add(iLevel, OutR); bool IsVirtSkleivOn = ((NegativTree != null) && (InpNegTerms != null) && (InpNegTerms.Count() != 0)); for (TreeFuncTerm.TreeNodeEnd x1 = X1Tree.EnumerationInit(); x1 != null; x1 = X1Tree.EnumerationNextNode()) { bool bIsSkleiv = false; byte[] pCurrTerm = X1Tree.EnumerationTerm; for (int iPos = 0; iPos < pCurrTerm.Length; iPos++) { byte cSymbSav = pCurrTerm[iPos]; if (cSymbSav == LogicFunction.cStarSymb) continue; // pCurrTerm[iPos] = (byte)(1 - cSymbSav); if (X1Tree.Contains(pCurrTerm)) { bIsSkleiv = true; //, if (cSymbSav == 1) { pCurrTerm[iPos] = LogicFunction.cStarSymb; // X2Tree.AddTerm(pCurrTerm); } } // , NegativTree else if (IsVirtSkleivOn && !NegativTree.Contains(pCurrTerm)) { bool bIsNotCanAdd = false; pCurrTerm[iPos] = LogicFunction.cStarSymb; // foreach (byte[] NegTerm in InpNegTerms) { if (bIsNotCanAdd = Dnf.Calculate(NegTerm, pCurrTerm)) break; } if (!bIsNotCanAdd) { bIsSkleiv = true; X2Tree.AddTerm(pCurrTerm); } } pCurrTerm[iPos] = cSymbSav; } // , if (!bIsSkleiv) OutR.AddLast((byte[])pCurrTerm.Clone()); } } // private static UInt64 GetTermCode(byte[] pTerm) { UInt64 iMultip = 1, iCode = 0; for (int i = 0; i < pTerm.Length; i++) { iCode += (iMultip * pTerm[i]); iMultip *= 3; } return iCode; } // private static byte[] GetTermByCode(UInt64 iCode, int iTermLength, byte[] pTerm = null) { if (pTerm == null) pTerm = new byte[iTermLength]; int iCounter = 0; while (iCode != 0) { pTerm[iCounter++] = (byte)(iCode % 3); iCode /= 3; } while (iCounter < iTermLength) pTerm[iCounter++] = 0; return pTerm; } // private static void Skleivanie(ICollection<UInt64> X1Tree, ICollection<UInt64> X2Tree, ICollection<UInt64> NegativTree, IEnumerable<byte[]> InpNegTerms, Dictionary<int, LinkedList<byte[]>> OutResult, int iLevel, int iTermLength) { LinkedList<byte[]> OutR = new LinkedList<byte[]>(); if (OutResult != null) OutResult.Add(iLevel, OutR); byte[] pCurrTerm = new byte[iTermLength]; bool IsVirtSkleivOn = ((NegativTree != null) && (InpNegTerms != null) && (InpNegTerms.Count() != 0)); foreach (UInt64 x1 in X1Tree) { GetTermByCode(x1, iTermLength, pCurrTerm); bool bIsSkleiv = false; UInt64 iMultip = 1; for (int iPos = 0; iPos < iTermLength; iPos++) { byte cSymbSav = pCurrTerm[iPos]; //(byte)((x1 / iMultip) % 3); if (cSymbSav != LogicFunction.cStarSymb) { UInt64 iCode = (cSymbSav == 0 ? x1 + iMultip : x1 - iMultip); // if (X1Tree.Contains(iCode)) { bIsSkleiv = true; //, if (cSymbSav == 1) { X2Tree.Add(x1 + iMultip); } } // , NegativTree else if (IsVirtSkleivOn && !NegativTree.Contains(iCode)) { bool bIsNotCanAdd = false; pCurrTerm[iPos] = LogicFunction.cStarSymb; // foreach (byte[] NegTerm in InpNegTerms) { if (bIsNotCanAdd = Dnf.Calculate(NegTerm, pCurrTerm)) break; } pCurrTerm[iPos] = cSymbSav; if (!bIsNotCanAdd) { bIsSkleiv = true; X2Tree.Add(x1 + (byte)(LogicFunction.cStarSymb - cSymbSav) * iMultip); } } } iMultip *= 3; } // , if (!bIsSkleiv) OutR.AddLast((byte[])pCurrTerm.Clone()); } } // // private static void DeleteDublicatingTerms(IEnumerable<byte[]> InX1, ICollection<UInt64> OutX2Tree) { OutX2Tree.Clear(); foreach (byte[] x1 in InX1) { UInt64 iCode = GetTermCode(x1); if (OutX2Tree.Contains(iCode)) continue; OutX2Tree.Add(iCode); } } // // private static void DeleteDublicatingTerms(IEnumerable<byte[]> InX1, TreeFuncTerm OutX2Tree) { OutX2Tree.Clear(); foreach (byte[] x1 in InX1) OutX2Tree.AddTerm(x1); } // private static bool IsEqualTerms(byte[] pTermC, byte[] pTermB) { if ((pTermC == null) || (pTermB == null) || (pTermC.Length != pTermB.Length)) return false; bool bIsEqual = false; int iLength = Math.Min(pTermC.Length, pTermB.Length); for ( int i = 0; i < iLength; i++) { if (!(bIsEqual = (pTermB[i] == pTermC[i]))) break; } return bIsEqual; } // private static void ReduceRedundancyTerms(LinkedList<byte[]> InpTerms, Dictionary<int, LinkedList<byte[]>> SkleivTerms, ICollection<byte[]> ResultTerms) { if ((InpTerms == null) || (SkleivTerms == null) || (ResultTerms == null)) return; // ResultTerms.Clear(); // , Dictionary<byte[], HashSet<byte[]>> Outputs2Inputs = new Dictionary<byte[], HashSet<byte[]>>(); // , Dictionary<byte[], HashSet<byte[]>> Inputs2Outputs = new Dictionary<byte[], HashSet<byte[]>>(); // foreach (int iLevel in SkleivTerms.Keys.OrderByDescending(p => p).AsEnumerable()) { // foreach (byte[] outTerm in SkleivTerms[iLevel]) { // , term HashSet<byte[]> InpTermsLst = new HashSet<byte[]>(); // foreach (byte[] inpTerm in InpTerms) { if (Dnf.Calculate(inpTerm, outTerm)) { InpTermsLst.Add(inpTerm); if (!Inputs2Outputs.ContainsKey(inpTerm)) Inputs2Outputs.Add(inpTerm, new HashSet<byte[]>()); Inputs2Outputs[inpTerm].Add(outTerm); } } Outputs2Inputs.Add(outTerm, InpTermsLst); } } // - Inputs2Outputs = Inputs2Outputs.OrderBy(p => p.Value.Count).ToDictionary(p => p.Key, v => v.Value); // , - while (Inputs2Outputs.Count > 0) { byte[] outTerm = Inputs2Outputs.First().Value.OrderByDescending(q => Outputs2Inputs[q].Count()).First(); ResultTerms.Add(outTerm); foreach (byte[] inpTerm in Outputs2Inputs[outTerm].ToArray()) { foreach (byte[] outTerm2Del in Inputs2Outputs[inpTerm]) Outputs2Inputs[outTerm2Del].Remove(inpTerm); Inputs2Outputs.Remove(inpTerm); } } } // public static void LogicFuncMinimize(IEnumerable<byte[]> PositivTerms, ICollection<byte[]> OutPos, IEnumerable<byte[]> NegativTerms, ICollection<byte[]> OutNeg) { int iTotalLevels = (PositivTerms.Count() > 0 ? PositivTerms.First().Length : (NegativTerms != null && NegativTerms.Count() > 0 ? NegativTerms.First().Length : 0)); Dictionary<int, LinkedList<byte[]>> SkleivPosTerms = new Dictionary<int, LinkedList<byte[]>>(iTotalLevels); Dictionary<int, LinkedList<byte[]>> SkleivNegTerms = new Dictionary<int, LinkedList<byte[]>>(iTotalLevels); LinkedList<byte[]> InpPosTerms = new LinkedList<byte[]>(); LinkedList<byte[]> InpNegTerms = new LinkedList<byte[]>(); if (iTotalLevels < 40) { HashSet<UInt64> X1PositivTree = new HashSet<UInt64>(); DeleteDublicatingTerms(PositivTerms, X1PositivTree); HashSet<UInt64> X1NegativTree = null; if (NegativTerms != null) { X1NegativTree = new HashSet<UInt64>(); DeleteDublicatingTerms(NegativTerms, X1NegativTree); // foreach(UInt64 iNumb in X1PositivTree.Intersect(X1NegativTree)) { // - X1 NegativTerms int iPos_Count = PositivTerms.Count(p => GetTermCode(p) == iNumb); int iNeg_Count = NegativTerms.Count(p => GetTermCode(p) == iNumb); if (iPos_Count > iNeg_Count) { X1NegativTree.Remove(iNumb); } else if (iPos_Count < iNeg_Count) { X1PositivTree.Remove(iNumb); } else //if (iPos_Count == iNeg_Count) { X1PositivTree.Remove(iNumb); X1NegativTree.Remove(iNumb); } } // foreach (UInt64 code in X1NegativTree) { InpNegTerms.AddLast(GetTermByCode(code, iTotalLevels)); } } // foreach (UInt64 code in X1PositivTree) { InpPosTerms.AddLast(GetTermByCode(code, iTotalLevels)); } int iLevelCounter = 0; // while ((X1PositivTree.Count != 0) && (iLevelCounter < iTotalLevels)) { HashSet<UInt64> X2PositivTree = new HashSet<UInt64>(); Skleivanie(X1PositivTree, X2PositivTree, X1NegativTree, InpNegTerms, SkleivPosTerms, iLevelCounter, iTotalLevels); if ((X1NegativTree != null) && (X1NegativTree.Count != 0)) { HashSet<UInt64> X2NegativTree = new HashSet<UInt64>(); Skleivanie(X1NegativTree, X2NegativTree, X1PositivTree, InpPosTerms, SkleivNegTerms, iLevelCounter, iTotalLevels); // X1NegativTree.Clear(); X1NegativTree = X2NegativTree; } // X1PositivTree.Clear(); X1PositivTree = X2PositivTree; iLevelCounter++; GC.Collect(); } } else { TreeFuncTerm X1PositivTree = new TreeFuncTerm(); DeleteDublicatingTerms(PositivTerms, X1PositivTree); TreeFuncTerm X1NegativTree = null; if (NegativTerms != null) { X1NegativTree = new TreeFuncTerm(); DeleteDublicatingTerms(NegativTerms, X1NegativTree); // for (TreeFuncTerm.TreeNodeEnd x1 = X1PositivTree.EnumerationInit(); x1 != null; x1 = X1PositivTree.EnumerationNextNode()) { if (!X1NegativTree.Contains(X1PositivTree.EnumerationTerm)) continue; // - PositivTerms NegativTerms int iPos_Count = PositivTerms.Count(p => IsEqualTerms(p, X1PositivTree.EnumerationTerm)); int iNeg_Count = NegativTerms.Count(p => IsEqualTerms(p, X1PositivTree.EnumerationTerm)); if (iPos_Count > iNeg_Count) { X1NegativTree.Remove(X1PositivTree.EnumerationTerm); } else if (iPos_Count < iNeg_Count) { X1PositivTree.Remove(X1PositivTree.EnumerationTerm); } else //if (iPos_Count == iNeg_Count) { X1PositivTree.Remove(X1PositivTree.EnumerationTerm); X1NegativTree.Remove(X1PositivTree.EnumerationTerm); } } // for (TreeFuncTerm.TreeNodeEnd x1 = X1NegativTree.EnumerationInit(); x1 != null; x1 = X1NegativTree.EnumerationNextNode()) { InpNegTerms.AddLast((byte[])X1NegativTree.EnumerationTerm.Clone()); } } // for (TreeFuncTerm.TreeNodeEnd X1Term = X1PositivTree.EnumerationInit(); X1Term != null; X1Term = X1PositivTree.EnumerationNextNode()) { InpPosTerms.AddLast((byte[])X1PositivTree.EnumerationTerm.Clone()); } int iLevelCounter = 0; // while ((X1PositivTree.Count != 0) && (iLevelCounter < iTotalLevels)) { TreeFuncTerm X2PositivTree = new TreeFuncTerm(); Skleivanie(X1PositivTree, X2PositivTree, X1NegativTree, InpNegTerms, SkleivPosTerms, iLevelCounter); if ((X1NegativTree != null) && (X1NegativTree.Count != 0)) { TreeFuncTerm X2NegativTree = new TreeFuncTerm(); Skleivanie(X1NegativTree, X2NegativTree, X1PositivTree, InpPosTerms, SkleivNegTerms, iLevelCounter); // X1NegativTree.Clear(); X1NegativTree = X2NegativTree; } // X1PositivTree.Clear(); X1PositivTree = X2PositivTree; iLevelCounter++; GC.Collect(); } } // ReduceRedundancyTerms(InpPosTerms, SkleivPosTerms, OutPos); // ReduceRedundancyTerms(InpNegTerms, SkleivNegTerms, OutNeg); } // public void Start(IEnumerable<byte[]> TermsInput) { LogicFuncMinimize(TermsInput, _result.Terms, null, null); } // public void Start(IEnumerable<byte[]> TermsInput, IEnumerable<byte[]> NegativTerms) { LogicFuncMinimize(TermsInput, _result.Terms, NegativTerms, _resultNeg.Terms); } // public void Start(IEnumerable<char[]> TermsInput) { Start(TermsInput.Select(t => t.Select(p => (byte)(p == '0' ? 0 : 1)).ToArray())); } // public void Start(IEnumerable<char[]> TermsInput, IEnumerable<char[]> NegativTerms) { Start(TermsInput.Select(t => t.Select(p => (byte)(p == '0' ? 0 : 1)).ToArray()), NegativTerms.Select(t => t.Select(p => (byte)(p == '0' ? 0 : 1)).ToArray())); } // public void Start(IEnumerable<bool[]> TermsInput) { Start(TermsInput.Select(t => t.Select(p => (byte)(p ? 1 : 0)).ToArray())); } // public void Start(IEnumerable<bool[]> TermsInput, IEnumerable<bool[]> NegativTerms) { Start(TermsInput.Select(t => t.Select(p => (byte)(p ? 1 : 0)).ToArray()), NegativTerms.Select(t => t.Select(p => (byte)(p ? 1 : 0)).ToArray())); } public void PrintResult() { Console.WriteLine("--------Otvet-------"); char[] pTermSymbs = new char[] { '0', '1', '*' }; foreach (byte[] Term in _result.Terms) { for (int j = 0; j < Term.Length; j++) { Console.Write(pTermSymbs[Term[j]].ToString() + " "); } Console.WriteLine(); } } }
الفئة Quine_McCluskey هي تطبيق لهذه الخوارزمية التي تستخدم فئات وواجهات أخرى: Dnf و TreeNodeBase و TreeNodeMiddle و TreeNodeEnd و TreeFuncTerm. لبدء التحسين ، تحتاج إلى استدعاء إحدى طرق البدء المحملة بشكل زائد ، والتي تستدعي وظيفة LogicFuncMinimize ، حيث يتم في الواقع تنفيذ خوارزمية التصغير. يتم تنفيذ آلية التقليل في نسختين:
• استخدام الحاوية .NET SortedSet لتخزين مصطلحات البحث والبحث عنها.
• بدون استخدام حاويات .NET على أساس الشجرة الثلاثية TreeFuncTerm.
من حيث السرعة ، هذان الخياران متساويان تقريبًا (مع حاويات .NET ، ربما أسرع قليلاً ، ولكن ليس دائمًا) ، ولكن الحاجة إلى تطبيق TreeFuncTerm ترجع إلى عدة عوامل:
• الخيار الأول ، استنادًا إلى رموز تجزئة عدد صحيح 64 بت والبحث في قاموس SortedSet .NET ، يعمل بشكل صحيح فقط مع عدد متغيرات الإدخال من حيث تصل إلى 40 ، ومع عدد أكبر يتجاوز شبكة رمز تجزئة عدد صحيح 64 بت ، تستخدم لتشغيل الحاوية. في الواقع ، بما أن المنطق الثلاثي يُستخدم في المصطلحات الملصقة داخل الخوارزمية ، فعندئذٍ مع عدد متغيرات الإدخال تساوي 41 ، فإن القيمة القصوى لشفرة التجزئة
يتجاوز بالفعل القيمة القصوى
-1 ، والتي يمكن كتابتها إلى متغير 64 بت. باستخدام المزيد من المتغيرات ، يتم استخدام خيار بناءً على شجرة البحث الثلاثية للمؤلف TreeFuncTerm.
• من الضروري التحقق من تشغيل التطبيق على حاويات .NET مع تنفيذ آخر مستقل عنه.
• أنت تحتاج فقط إلى خيار خالٍ من حاويات .NET ، والتي يمكن تنفيذها بسهولة على الأنظمة الأساسية التي لا توجد فيها منصة .NET (على سبيل المثال ، في وحدات التحكم الدقيقة ، و FPGA ، وما إلى ذلك).
يعتمد تشغيل شجرة البحث TreeFuncTerm على تكوين الارتباطات إلى فئتي TreeNodeMiddle و TreeNodeEnd ، وهي عمليات تنفيذ لواجهة TreeNodeBase. الفئة TreeNodeMiddle هي عقدة وسيطة للشجرة ، والفئة TreeNodeEnd هي نهاية الشجرة. باستخدام الدالتين EnumerationInit () و EnumerationNextNode () ، يتم تنفيذ آلية غير متكررة لتعداد جميع أوراق الشجرة لـ TreeNodeEnd في الشجرة. تقوم الدالة EnumerationInit () بتهيئة التعداد وإرجاع الورقة الأولى في الشجرة. ترجع الدالة EnumerationNextNode () صفحة الشجرة التالية أو NULL إذا لم يعد هناك أوراق للتحديد. علاوة على ذلك ، فإن البنية الداخلية المساعدة EnumerationTerm ، والتي تعكس موضع البحث "المؤشر" داخل الشجرة ، هي أيضًا رمز مصطلح الورقة الموجودة في المنطق الثلاثي {0،1،2}. وتجدر الإشارة إلى أن ترتيب اختيار الأوراق من الشجرة لا يتزامن مع ترتيب إضافتها إليها.
يمكن تقسيم الخوارزمية لأغراض وظيفية إلى ثلاث مراحل.
- تحضير. لحل المشكلة المذكورة أعلاه المتمثلة في القضاء على تعداد الخيارات لتعريفات إضافية في التنفيذ قيد النظر ، تتلقى الدالة LogicFuncMinimize مجموعتي بيانات أوليتين PositivTerms و NegativTerms ، حيث تقبل الوظيفة المحسنة القيم الحقيقية (TRUE ، 1) والقيم الخاطئة (FALSE ، 0) ، على التوالي. بادئ ذي بدء ، يتم فحص هذه القوائم للتأكد من اتساق البيانات المصدر. من الضروري ضمان احتواء كل مجموعة من مجموعات البيانات فقط على مصطلحات فريدة موجودة فقط في أي من القوائم. لضمان ذلك ، يتم فحص كل مصطلح إدخال فريد ويتم العثور على عدد الإدخالات في كل قائمة من قوائم المصادر. إذا ظهر المصطلح في كلتا القائمتين ، فإنه يبقى فقط في القائمة التي يظهر فيها أكثر ، ويتم حذفه من الآخر. إذا حدث المصطلح في كل قائمة بشكل متساوٍ ، فسيتم إزالته من كلتا القائمتين ، مما يضمن التفرد.
- الترابط. بعد ذلك ، يتم تنفيذ دورة تكرارية لمصطلحات الإدخال. عند كل تكرار ، بعبارات لاصقة ، تتم إضافة علامة واحدة * للموضع الملصق. لذلك ، لا يمكن أن يكون عدد التكرارات أكبر من عدد المتغيرات N. على عكس التنفيذ السابق ، فإن وظيفة Skleivanie لمصطلحات المدخلات لديها القدرة على الغراء ليس فقط مع المصطلحات من قائمتها ، ولكن أيضًا في حالة عدم وجود مصطلح بفارق واحد أيضًا مع المصطلحات "الافتراضية". نعني بالمصطلحات "الافتراضية" مصطلحات محددة بشكل مصطنع لا توجد في أي من قوائم مصطلحات مجموعة من المستوى الحالي. لكن التصاق ممكن فقط إذا كان المصطلح "الظاهري" لا يغطي مصطلحًا واحدًا من المجموعة الأولية من القائمة المقابلة.
يتم استدعاء وظيفة Skleivanie لمعالجة القوائم في كل تكرار مرتين بحيث يتطابق معنى استخدام قوائم PositivTerms و NegativTerms مع محتواها الفعلي في المكالمة الأولى ، وفي المكالمة الثانية ، يتم تبديل قوائم PositivTerms و NegativTerms من حيث الاستخدام ، أي أنه يعتبر تحتوي قائمة PositivTerms على مصطلحات سلبية ، وتحتوي قائمة NegativTerms على مصطلحات إيجابية:
Skleivanie (X1PositivTree، ...، X1NegativTree، ...، SkleivTerms، ...)؛
Skleivanie (X1NegativTree، ...، X1PositivTree، ...، null، ...)؛
وبالتالي ، يحدث لصق متزامن ومتزامن لمصطلحات قائمتين.
إذا لم يكن هناك مصطلح آخر يختلف عن المصطلح في موضع واحد فقط ، لا حقيقي ولا افتراضي ، أي أن المصطلح لا يلتصق مع أي شخص ، عندئذٍ يعتبر من نتائج الخطوة 1 من الخوارزمية ، يتم استبعاده من المزيد من العمل فيه ويذهب لإدخال المرحلة 2 من الخوارزمية التي تم تنفيذها في إجراء ReduceRedundancyTerms. يتم إرسال المصطلحات غير الملصقة إلى إخراج الخوارزمية فقط على هذا الاستدعاء لوظيفة Skleivanie ، والتي يتزامن معها استخدام قوائم PositivTerms و NegativTerms مع تعبئتها الفعلية ، أي في المكالمة الأولى. - اختصار. يتم تجاهل المصطلحات الملصقة الزائدة في ReduceRedundancyTerms باستخدام خوارزمية لحل تقريبي لمشكلة تغطية المجموعة الأصلية بمجموعات فرعية متغيرة الطول. يتم توفير التغطية الأقرب إلى الأقصر من خلال خوارزمية تحويل جدول التغطية (TP) ، استنادًا إلى طريقة "الحد الأدنى للعمود - الحد الأقصى للصف" (والتي يمكن رؤيتها ، على سبيل المثال ، هنا http://www.studfiles.ru/preview/5175815/page:4 ) .
المنطق التقريبي لعمله هو كما يلي:
0. يعتبر الجدول الأصلي هو TP المحول الحالي ، مجموعة خطوط التغطية فارغة ؛
1. يتم تمييز العمود الذي يحتوي على أقل عدد من الوحدات في الجدول الحالي. من بين الصفوف التي تحتوي على وحدات في هذا العمود ، يتم تمييز واحد به أكبر عدد من الوحدات. يتم تضمين هذا الصف في التغطية ، ويتم تقليل الجدول الحالي عن طريق حذف جميع الأعمدة التي يحتوي فيها الصف المحدد على وحدة.
2. في حالة عدم وجود أعمدة متقاطعة في الجدول ، يتم تنفيذ الخطوة 1 ؛ وإلا ، يتم إنشاء التغطية. ملاحظة: عند حساب عدد الوحدات المتتالية ، يتم أخذ الوحدات في الأعمدة غير المميزة بعين الاعتبار.
تعمل هذه الخوارزمية بسرعة كافية وتعطي نتيجة قريبة من المثلى.
لاختبار تشغيل الخوارزمية ، يُقترح استخدام وظيفة الاختبار TestQuineMcCluskeyRandomPart ، والتي ، من إجمالي مجموعة المصطلحات المحتملة ، هي يختار عشوائيًا فقط الجزء المعطى 0 <dPart <= 1 (هو معلمة الوظيفة) ، والتي يتم إجراء التحسين لها. باستخدام المعلمة dPart <1 ، سيتم الحصول على مجموعة مبتورة من مصطلحات الإدخال ، ومع dPart = 1 ، سيتم الحصول على مجموعة كاملة من بيانات الإدخال.
TestQuineMcCluskeyRandomPart (انقر للعرض) public static void TestQuineMcCluskeyRandomPart(int iVariableAmount, double dPart=1) { if (dPart < 0) throw new ArgumentException(" dPart 0 1"); if (dPart > 1) dPart = 1; // ulong iTotalCombines = (ulong)1 << iVariableAmount; LinkedList<byte[]> pTrueCol = new LinkedList<byte[]>(); LinkedList<byte[]> pFalseCol = new LinkedList<byte[]>(); HashSet<ulong> pUsedTerms = new HashSet<ulong>(); Random rnd = new Random(); byte[] buf = new byte[8]; while (pUsedTerms.LongCount() < (iTotalCombines * dPart)) { rnd.NextBytes(buf); ulong iCurValue = (ulong)BitConverter.ToInt64(buf, 0) % iTotalCombines; if (pUsedTerms.Contains(iCurValue)) { // - do { iCurValue = ++iCurValue % iTotalCombines; } while (pUsedTerms.Contains(iCurValue)); } pUsedTerms.Add(iCurValue); byte[] sLine = new byte[iVariableAmount]; for (int i = 0; i < iVariableAmount; i++) { sLine[i] += (byte)(iCurValue % 2); iCurValue >>= 1; } if (rnd.Next(2) != 0) { pTrueCol.AddLast(sLine); } else { pFalseCol.AddLast(sLine); } } // DateTime DtStart = DateTime.Now; Console.WriteLine(" - " + DtStart.ToLongTimeString()); Quine_McCluskey Logic = new Quine_McCluskey(); Logic.Start(pTrueCol, pFalseCol); DateTime DtEnd = DateTime.Now; Logic.PrintResult(); Console.WriteLine(" - " + DtStart.ToLongTimeString()); Console.WriteLine(" - " + DtEnd.ToLongTimeString()); TimeSpan Elapsed = DtEnd - DtStart; Console.WriteLine(" - " + String.Format("{0:00}:{1:00}:{2:00}", Elapsed.Hours, Elapsed.Minutes, Elapsed.Seconds)); // int iErrorsCounter = 0; foreach (byte[] kvp in pTrueCol) { if (Logic.Result.Calculate(kvp) != true) iErrorsCounter++; } foreach (byte[] kvp in pFalseCol) { if (Logic.Result.Calculate(kvp) != false) iErrorsCounter++; } Console.WriteLine("- = " + pUsedTerms.Count); Console.WriteLine("- = " + Logic.Result.Terms.Count); Console.WriteLine("- = " + iErrorsCounter); Console.ReadLine(); }
نتيجة لوظيفة الاختبار ، يتم حساب عدد المصطلحات في الحد الأدنى من الشكل العادي المفصل وعدد الأخطاء التي تغطيها مع مجموعة المصطلحات الأصلية.
في الختام ، أود أن أشير إلى أن هذا التطبيق للخوارزمية أثبت عمليًا أنه وسيلة فعالة وموثوقة لتقليل الوظائف المنطقية المحددة بواسطة مجموعتين غير مكتملتين من المصطلحات التي تأخذ فيها الوظيفة المنطقية قيم TRUE و FALSE ، على التوالي. بالطبع ، يمكن استخدام هذا التطبيق أيضًا في الشكل الكلاسيكي في حالة وظيفة إدخال منطقية محددة بالكامل ، عندما يتم إدخال قائمة مصطلحات واحدة أو أخرى فقط. على سبيل العيب ، من الضروري التحقق في وظيفة Skleivanie من عدم وجود أخطاء في التغطية لكل مصطلح افتراضي لقائمة مصطلحات المصدر الكاملة عند كل تكرار للخوارزمية ، مما يؤدي إلى تكاليف زمنية كبيرة مع عدد كبير من مصطلحات الإدخال.