نكتب "آلة حاسبة". الجزء الثاني حل المعادلات ، وجعل في LaTeX ، تسريع وظائف لتسليط الضوء

تحية!

لذلك ، في الجزء الأول ، قمنا بالفعل بعمل جيد من خلال القيام بالمشتقات والتبسيط والتجميع. لذا ، ماذا يجب أن تفعل لدينا آلة حاسبة بسيطة؟ حسنا ، على الأقل معادلات النموذج

( x - b ) ( t a n ( s i n ( x ) ) 2 - 3 t a n ( s i n ( x ) ) + c ) = 0    

بالتأكيد يجب أن تقرر. كما أنه من الجميل رسم هذه الحالة في وقت متأخر ، وسيكون على ما يرام! دعنا نذهب!





تنصل
على الرغم من أن الكود موجود هنا في C # ، إلا أنه صغير جدًا هنا بحيث لا يؤدي عدم معرفة اللغة أو الكراهية إلى تعقيد القراءة إلى حد كبير.


تجميع التسارع


في الجزء الأخير ، قمنا بتجميع الوظيفة بحيث يمكننا تجميعها مرة واحدة ، ثم تشغيلها عدة مرات في المستقبل.
لذلك ، نقدم الوظيفة

 s i n ( x 2 ) + c o s ( x 2 ) + x 2 + s i n ( x 2 )  


في نهاية الجزء الأول ، كانت سرعة هذه الوظيفة على النحو التالي:
طريقةالوقت (بالنانوثانية)
استبدل6800
لدينا وظيفة المترجمة650
هذه هي وظيفة مكتوبة مباشرة في التعليمات البرمجية.430

ماذا؟
البديل - طريقة كلاسيكية لحساب قيمة التعبير ، على سبيل المثال
var x = MathS.Var("x"); var expr = x + 3 * x; Console.WriteLine(expr.Substitute(x, 5).Eval()); >>> 20 

وظيفتنا المترجمة هي عندما نفعل الشيء نفسه ، ولكن بعد تجميعها
 var x = MathS.Var("x"); var expr = x + 3 * x; var func = expr.Compile(x); Console.WriteLine(func.Substitute(5)); 

وظيفة مكتوبة مباشرة في التعليمات البرمجية هي عندما نفعل
 static Complex MyFunc(Complex x) => x + 3 * x; 


كما نرى ، هناك أجزاء مكررة في هذه الوظيفة ، على سبيل المثال ، × 2 ، وسيكون من الجيد تخزينها مؤقتًا.

للقيام بذلك ، نقدم اثنين من التعليمات الأخرى PULLCACHE و TOCACHE. الأولى ستدفع الرقم في ذاكرة التخزين المؤقت على العنوان الذي نمره إلى المكدس. سيقوم الثاني بنسخ ( stack.Peek() ) آخر رقم من المكدس إلى ذاكرة التخزين المؤقت ، وكذلك في عنوان محدد.

يبقى أن نضع جدولًا فيه أثناء التجميع سنكتب وظائف للتخزين المؤقت. ما نحن لن مخبأ؟ حسنًا ، أولاً ، ماذا يحدث مرة واحدة. التعليمات الإضافية للوصول إلى ذاكرة التخزين المؤقت ليست جيدة. ثانياً ، العمليات البسيطة جدًا لا تجعل التخزين المؤقت ذا معنى. حسنًا ، على سبيل المثال ، الوصول إلى متغير أو رقم.

عند تفسير قائمة الإرشادات ، سيكون لدينا صفيف تم إنشاؤه مسبقًا للتخزين المؤقت. الآن تبدو التعليمات الخاصة بهذه الوظيفة

 PUSHCONST (2, 0) PUSHVAR 0 CALL powf TOCACHE 0 #   ,         , -  . CALL sinf TOCACHE 1 #      PULLCACHE 0 # ,   .    PULLCACHE 0 CALL cosf PULLCACHE 1 CALL sumf CALL sumf CALL sumf 


بعد ذلك نحصل على نتيجة أفضل بوضوح:
طريقةالوقت (بالنانوثانية)
Subtitute6800
لدينا وظيفة المترجمة330 (كان 650)
هذه هي وظيفة مكتوبة مباشرة في التعليمات البرمجية.430

في اللفت ، يتم تنفيذ كل من تجميع وتفسير التعليمات هنا .

مطاط


هذا هو تنسيق معروف لتسجيل الصيغ الرياضية (وإن لم يكن فقط!) ، والتي يتم تقديمها في صور جميلة. إنه أيضًا على المحور ، وجميع الصيغ التي أكتبها مكتوبة فقط بهذا التنسيق.

وجود شجرة التعبير يجعل التقديم في اللاتكس بسيط جدا. كيف نفعل هذا من حيث المنطق؟ لذلك ، لدينا الجزء العلوي من الشجرة. إذا كان الرقم أو متغيرًا ، فكل شيء بسيط. إذا كانت هذه القمة ، على سبيل المثال ، عامل التقسيم ، نود المزيد  fracab من a/b ، لذلك للقسمة نكتب شيء من هذا القبيل

 public static class Div { internal static string Latex(List<Entity> args) => @"\frac{" + args[0].Latexise() + "}{" + args[1].Latexise() + "}"; } 


كل شيء بسيط جدا ، كما نرى. المشكلة الوحيدة التي واجهتها أثناء التنفيذ هي أنه من غير الواضح كيفية وضع الأقواس. إذا دفعناهم إلى كل مشغل ، فسوف نحصل على مثل هذا الهراء:

 left( left( left( left(x+3 right)+ left(a+b right) right) right)+c right)


ومع ذلك ، فإن الشخص اليقظ على الفور (وليس مثلي) يلاحظ أنه إذا تمت إزالته بالكامل ، فعندئذٍ عند إنشاء تعبير للنموذج c(a+b) ، سوف نطبع

ca+b



يتم حلها ببساطة ، ندخل أولويات المشغلين
 args[0].Latexise(args[0].Priority < Const.PRIOR_MUL) + "*" + args[1].Latexise(args[1].Priority < Const.PRIOR_MUL); 

وفويلا ، أنت جميلة.

Latexirization القيام به هنا . وكلمة اللاتكس لا وجود لها ، لقد اخترعت ذلك بنفسي ، لا تركلني على ذلك.

حل المعادلات


في الواقع ، من وجهة نظر الرياضيات ، لا يمكنك كتابة خوارزمية تجد جميع الحلول لبعض المعادلات. لذلك ، نريد أن نجد أكبر عدد ممكن من الجذور المختلفة ، ونحقق عدم إمكانية الوصول إلى الهدف النهائي. هناك مكونان: الحل العددي (كل شيء بسيط قدر الإمكان) والتحليلي (ولكن هذا هو التاريخ).

الحل العددي. طريقة نيوتن


انه بسيط للغاية ، معرفة الوظيفة f(x) سنبحث عن الجذر باستخدام صيغة تكرارية

xn+1=xn fracf(x)f(x)


نظرًا لأننا جميعًا نحل في طائرة معقدة ، فيمكننا أن نكتب بشكل أساسي دورة ثنائية الأبعاد تبحث عن حلول ثم تعود إلى حلول فريدة. علاوة على ذلك ، يمكننا الآن إيجاد مشتق الوظيفة تحليليًا ، ثم كلتا الدالتين f(x) و f(x) ترجمة.

نيوتن يجلس هنا .

الحل التحليلي


الأفكار الأولى واضحة جدا. لذلك ، التعبير ، جذور المعادلة a(x)b(x) مجموع متساو من الجذور a(x) و b(x) ، بالمثل للقسمة:

 internal static void Solve(Entity expr, VariableEntity x, EntitySet dst) { if (expr is OperatorEntity) { switch (expr.Name) { case "mul": Solve(expr.Children[0], x, dst); Solve(expr.Children[1], x, dst); return; case "div": Solve(expr.Children[0], x, dst); return; } } ... 


بالنسبة للجيب ، سيبدو هذا مختلفًا قليلاً:
 case "sinf": Solve(expr.Children[0] + "n" * MathS.pi, x, dst); return; 

بعد كل شيء ، نريد أن نجد كل الجذور ، وليس فقط تلك التي هي 0.

بعد التأكد من أن التعبير الحالي ليس منتجًا ، وليس مشغلين ووظائف أخرى مبسطة بسهولة ، نحتاج إلى محاولة العثور على قالب لحل المعادلة.

الفكرة الأولى هي استخدام الأنماط التي قمنا بها لتبسيط التعبير. وفي الواقع ، سوف نحتاج إلى ذلك تقريبًا ، ولكن أولاً نحتاج إلى القيام باستبدال متغير. وبالفعل ، إلى المعادلة

 sin(x)2+ sin(x)0.4=0


لا يوجد قالب ، ولكن للزوج

 startcasest2+t0.4=0t= sin(x) endcases


حتى كما هو موجود.

لذلك ، نجعل دالة من النوع GetMinimumSubtree ، والتي من شأنها أن ترجع البديل المتغير الأكثر كفاءة. ما هو بديل فعال؟ هذا هو هذا البديل الذي نحن
  1. تعظيم استخدام هذا الاستبدال
  2. تعظيم عمق الشجرة (بحيث في المعادلة  sin( sin(x))2+3 كان لدينا بديل  sin( sin(x)) )
  3. نتأكد من أننا مع هذا الاستبدال قمنا باستبدال جميع الإشارات إلى متغير ، على سبيل المثال ، إذا كانت في المعادلة  sin(x)2+ sin(x)+x سنجعل بديل t= sin(x) ثم كل شيء سيصبح سيئا. لذلك ، في هذه المعادلة ، أفضل بديل ل x إنه كذلك x (وهذا هو ، لا يوجد بديل جيد) ، ولكن على سبيل المثال في  sin(x)2+ sin(x)+c يمكننا أن نجعل بأمان بديل t= sin(x) .

بعد استبدال المعادلة تبدو أبسط بكثير.

متعدد الحدود


لذا ، فإن أول شيء نقوم به ، نقوم به expr.Expand() - افتح كل الأقواس من أجل التخلص من الوحل في النموذج x(x+2) تتحول إلى x2+2x . الآن ، بعد الكشف ، نحصل على شيء من هذا القبيل

c+x3+3x2ax3+x


لا الكنسي؟ ثم نجمع أولاً معلومات حول كل نموذج أحادي ، ونطبق "الأطفال الخطيين" (في المقال الأخير ) ونوسع كل المصطلحات.

ماذا لدينا عن المونوميال؟ المونوميل هو نتاج عوامل ، أحدها متغير ، أو عامل لدرجة متغير و عدد صحيح. لذلك ، سوف نقدم اثنين من المتغيرات ، واحد سوف يكون على درجة ، والآخر معامل. بعد ذلك ، فقط استعرض العوامل ، وفي كل مرة نتأكد من وجودها هناك x إلى حد ما ، أو بدون درجة على الإطلاق. إذا التقينا byaku - نعود مع فارغة.
byaka
إذا التقينا فجأة أي x3.2 . log(x،2) وغيرها من الأشياء التي ذكرها x ، ولكن لا يتناسب مع نمط أحادي - وهذا أمر سيء.


حسنًا ، قمنا بتجميع قاموس يكون فيه المفتاح هو درجة (عدد صحيح) وتكون القيمة معاملًا أحاديًا. هذا هو ما يبدو للمثال السابق:
 0 => c 1 => 1 2 => 3 3 => 1 - a 


هنا ، على سبيل المثال ، تنفيذ حل المعادلة التربيعية
 if (powers[powers.Count - 1] == 2) { var a = GetMonomialByPower(2); var b = GetMonomialByPower(1); var c = GetMonomialByPower(0); var D = MathS.Sqr(b) - 4 * a * c; res.Add((-b - MathS.Sqrt(D)) / (2 * a)); res.Add((-b + MathS.Sqrt(D)) / (2 * a)); return res; } 

هذه النقطة لم تكتمل بعد (على سبيل المثال ، تحتاج إلى القيام بذلك بشكل صحيح لمتعدد الحدود مكعب) ، ولكن بشكل عام يحدث هنا .

عكس الاجتياح


لذلك ، هنا قمنا ببعض استبدال النوع t= arcsin(x3+a2) ، والآن نريد الحصول على x من هناك. هنا لدينا مجرد نشر تدريجي للوظائف والمشغلين ، مثل

t= arcsin(x3+a2)


 sin(t)=x3+a2


 sin(t)a2=x3


( s i n ( t ) - a 2 ) f r a c 1 3 = x  



يبدو مقتطف الشفرة مثل هذا:
 switch (func.Name) { case "sinf": // sin(x) = value => x = arcsin(value) return FindInvertExpression(a, MathS.Arcsin(value), x); case "cosf": // cos(x) = value => x = arccos(value) return FindInvertExpression(a, MathS.Arccos(value), x); case "tanf": // tan(x) = value => x = arctan(value) return FindInvertExpression(a, MathS.Arctan(value), x); ... 

رمز هذه الوظائف هنا .

كل شيء ، الحل النهائي للمعادلات (وداعا!) يبدو شيء من هذا القبيل
  1. إذا علمنا بوجود وظيفة أو عامل صفر ، فقم بإرسال حل هناك (على سبيل المثال ، إذا a b = 0 ثم قم بتشغيل Solve (a) و Solve (b))
  2. العثور على بديل
  3. حل كما كثير الحدود إذا كان ذلك ممكنا
  4. بالنسبة لجميع الحلول ، قم بنشر بديل للحصول على الحل النهائي
  5. إذا ، كما متعدد الحدود ، لم يتم حلها ولدينا متغير واحد فقط ، فإننا حلها بطريقة نيوتن


لذلك ، نحن اليوم:
  • تسريع وظيفة المترجمة بسبب ذاكرة التخزين المؤقت
  • المقدمة في اللاتكس
  • لقد حللنا جزءًا صغيرًا من حالات المعادلات


ربما لن أتحدث عن الحساب العددي لبعض العناصر ، لأنه بسيط للغاية . لم أتحدث عن التحليل ، حيث تلقيت انتقادات حول هذا الأمر في مقال سابق. كانت الفكرة أن تكتب بنفسك. ومع ذلك ، هل من الضروري التحدث عن كيفية تحليل تعبير من سلسلة؟
المشروع هنا .

في الجزء التالي ...
إذا كان أي شخص آخر مهتمًا ، فسوف نحاول تعقيد حل المعادلات من خلال إضافة درجة مكعب أو رابع ، بالإضافة إلى نظام قابل للطي أكثر ذكاءً. ربما سنختار الأنماط ، لأن أي طالب سيقرر

1 sin(x)2+ cos(x)+0.5


ولكن ليس لدينا خوارزمية. علاوة على ذلك ، قد يكون هناك تكامل غير محدد (البداية). إن زيادة العدد إلى رباعيات أو مصفوفات لا معنى لها حتى الآن ، لكن بطريقة ما سيكون ذلك ممكنًا. إذا كانت لديك فكرة محددة عن الجزء الثالث ، فاكتب في الرسائل أو التعليقات الشخصية.


عن المشروع
أولئك الذين نظروا إلى الكود يمكن أن يلاحظوا كيف كان الخام وليس إعادة تشكيله. وبطبيعة الحال ، سأقوم بإعادة الرد ، وبالتالي فإن الفكرة الرئيسية لهذه المقالة هي نقل المعلومات من الناحية النظرية ، وعندها فقط يظهر في الكود. وطلبات السحب هي موضع ترحيب ، على الرغم من أننا ربما لا نتمكن من الوصول إلى wolfram.alpha. المشروع مفتوح.


وإليك بعض الأمثلة على ما فعلناه اليوم
 var x = MathS.Var("x"); var y = MathS.Var("y"); var expr = x.Pow(y) + MathS.Sqrt(x + y / 4) * (6 / x); Console.WriteLine(expr.Latexise()); >>> {x}^{y}+\sqrt{x+\frac{y}{4}}*\frac{6}{x} 

س ذ +قفصر س + F ص ل ج ص 4 *وصلج 6 س   

 var x = MathS.Var("x"); var expr = MathS.Sin(x) + MathS.Sqrt(x) / (MathS.Sqrt(x) + MathS.Cos(x)) + MathS.Pow(x, 3); var func = expr.Compile(x); Console.WriteLine(func.Substitute(3)); >>> 29.4752368584034 


 Entity expr = "(sin(x)2 - sin(x) + a)(b - x)((-3) * x + 2 + 3 * x ^ 2 + (x + (-3)) * x ^ 3)"; foreach (var root in expr.Solve("x")) Console.WriteLine(root); >>> arcsin((1 - sqrt(1 + (-4) * a)) / 2) >>> arcsin((1 + sqrt(1 + (-4) * a)) / 2) >>> b >>> 1 >>> 2 >>> i >>> -i 


شكرا لاهتمامكم!

Source: https://habr.com/ru/post/ar483294/


All Articles