φυβλαςのβλογ
บล็อกของ phyblas



manim บทที่ ๑๗: การใส่สูตรสมการทางคณิตศาสตร์
เขียนเมื่อ 2021/03/12 00:17
แก้ไขล่าสุด 2023/08/26 13:15

ต่อจาก บทที่ ๑๖

ในบทนี้จะพูดถึงการใส่สูตรสมการทางคณิตศาสตร์ต่างๆ




การสร้างสูตรสมการทางคณิตศาสตร์ด้วยคลาส Tex

คลาส Tex มีไว้สำหรับเขียนพวกสูตรสมการทางคณิตศาสตร์ขึ้นมาจากโค้ด LaTeX

อย่างไรก็ตาม ความสามารถตรงส่วนนี้มีความซับซ้อน และมีโอกาสเกิดบั๊กได้ง่าย คาดว่ายังน่าจะมีการเปลี่ยนแปลงอีกในอนาคต

สำหรับวิธีการเขียนโค้ด LaTeX นั้นจะไม่อธิบายในที่นี้ สามารถค้นตามเว็บเพื่อศึกษาเพิ่มเติมเอาเองได้

ขนาดของอักษรกำหนดได้ที่คีย์เวิร์ด font_size ส่วนสีกำหนดโดยคีย์เวิร์ด color

ตัวอย่าง ลองเขียนฟังก์ชันการแจกแจงเบตา
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        # ฟังก์ชันการแจกแจงเบตา
        beta = mnm.Tex(r'f(\alpha,\beta) = \frac{x^{\alpha-1}(1-x)^{\beta-1} }{B(\alpha,\beta)}',font_size=96)
        # แทนค่า α=2, β=3 ลงสมการ
        beta23 = mnm.Tex(r'f(2,3) = \frac{x^{2-1}(1-x)^{3-1} }{B(2,3)}',font_size=96,color='#a4ff9a')
        self.play(
            mnm.Transform(beta,beta23),
            run_time=1.5
        )
        self.wait(0.5)



สามารถใส่ค่า fill_opacity กับ stroke_width เพื่อแสดงตัวอักษรเป็นโครงขอบได้

ตัวอย่าง เขียนฟังก์ชันการแจกแจงปัวซง
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        poisson = mnm.Tex(r'f(\lambda) = \frac{\lambda^x \exp(-\lambda)}{x!}',
                          font_size=144,
                          fill_opacity=0,
                          stroke_width=4)
        poisson6 = mnm.Tex(r'f(6) = \frac{6^x \exp(-6)}{x!}',
                           font_size=144,
                           color='#9addff',
                           fill_opacity=0,
                           stroke_width=4)
        self.play(
            mnm.Transform(poisson,poisson6),
            run_time=1.5
        )
        self.wait(0.5)






การแยกส่วนประกอบ

การสร้าง Tex ดังในตัวอย่างที่ผ่านมานั้น ส่วนประกอบทั้งหมดใน Tex จะถือว่าเป็นวัตถุตัวเดียวกันหมด ซึ่งถ้าหากไม่ใช่ว่าเราตั้งใจจะนำมาแยกส่วนเพื่อทำอะไรก็ไม่ได้มีปัญหาอะไร

แต่ในกรณีส่วนใหญ่นั้นเวลาแสดงภาพเคลื่อนไหวจำเป็นต้องทำการแยกส่วนประกอบต่างๆออกจากกันให้ชัดเจนไม่เช่นนั้นจะเกิดข้อผิดพลาดหรือไม่สามารถแสดงการเคลื่อนไหวที่ถูกต้องได้

การแยกส่วนประกอบภายใน Tex นั้นมีอยู่หลายวิธี โดยพื้นฐานแล้ววิธีที่ง่ายและแน่นอนที่สุดก็คือทำการแยกส่วนโดยใส่ไปทีละชิ้น

เช่นลองเขียนฟังก์ชันการแจกแจงแบร์นุลลี โดยทำการแยกส่วน อาจเขียนได้ดังนี้
mnm.Tex('f','(','p',')','=','p','^x','(','1','-','p)','^{1','-','x}')

ซึ่งการที่ต้องมาเขียนแยกแบบนี้ก็ดูแล้วยุ่งยากและยังเข้าใจยากด้วย โดยทั่วไปจึงแนะนำให้ใช้วิธีการใส่ส่วนประกอบที่ต้องการแยกลงในคีย์เวิร์ด isolate แล้วส่วนประกอบที่ระบุทั้งหมดจะถูกแยกส่วนโดยอัตโนมัติ

ตัวอย่างการใช้
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        bernoulli = mnm.Tex(r'f(p) = p^x(1-p)^{1-x}',
                            isolate=['f','p','=','x','-','1'])
        # แทนที่จะเขียนแยกส่วนเป็น
        # bernoulli = mnm.Tex('f','(','p',')','=','p','^x','(','1','-','p)','^{1','-','x}')
        bernoulli.set_color_by_gradient('#ff9add','#a9f2be')
        self.play(
            bernoulli.animate.set_width(13),
            run_time=1.5
        )



อนึ่ง ในตัวอย่างนี้ทำการแยกส่วนเพื่อให้สามารถใส่สีให้แต่ละส่วนแบบไล่สีด้วย .set_color_by_gradient() ได้ หากไม่แยกส่วนก็จะถือว่าทั้งก้อนเป็นวัตถุชิ้นเดียวและไม่สามารถเขียนให้ไล่สีได้




การแปลงส่วนประกอบด้วย TransformMatchingTex

ประโยชน์ของการแยกส่วนประกอบของวัตถุ Tex จะเห็นชัดเมื่อเวลาที่ทำการแปลงร่างวัตถุ โดยเฉพาะเมื่อใช้การแปลงด้วยคลาส TransformMatchingTex

TransformMatchingTex ใช้สำหรับแปลงร่าง Tex โดยเทียบส่วนประกอบที่เหมือนกันแล้วทำการย้ายค่าแทนที่ตามความเหมาะสม

ตัวอย่าง ลองสร้าง Tex ขึ้นมา ๒ ตัวโดยแยกส่วนประกอบหมดเหมือนกัน แล้วใช้ TransformMatchingTex ทำการแปลงดู
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        bernoulli = mnm.Tex(r'f(p) = p^x(1-p)^{1-x}',
                            isolate=['f','p','=','x','-','1'],
                            font_size=144)
        bernoulli07 = mnm.Tex(r'f(0.7) = 0.7^x(1-0.7)^{1-x}',
                             isolate=['f','0.7','=','x','-','1'],
                             font_size=112,
                             color='#a9f2ee')
        self.play(
            mnm.TransformMatchingTex(bernoulli,
                                     bernoulli07,
                                     key_map={'p':'0.7'}),
            run_time=1.5
        )
        self.wait(0.5)



ผลที่ได้จะเห็นว่า 0.7 เข้าแทนที่ p ส่วนที่เหลือไม่มีการเปลี่ยนแปลงก็อยู่อย่างนั้น

การใส่ key_map={'p':'0.7'} นั้นเพื่อเป็นการบอกให้รู้ว่า p ให้แปลงเป็น 0.7

หากไม่ใส่ key_map ไว้ ผลที่ได้ก็จะกลายเป็นแค่ p หายไปแล้ว 0.7 ปรากฏขึ้นมาแทน แบบนี้



นอกจากนี้ หากไม่ได้ทำการแยกส่วนไว้ ผลที่ได้ก็จะต่างออกไปมาก หากลองทำเหมือนเดิมแต่เอาส่วน isolate ออกแล้วผลที่ได้จะเป็นแบบนี้



ซึ่งจะเห็นว่าเมื่อไม่มีการแยกส่วนก็จะแค่ทำให้ทั้งก้อนหายไปหมดแล้วแทนด้วยก้อนใหม่

ลองดูอีกตัวอย่างเพื่อให้เห็นภาพการใช้งานชัดขึ้น
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        tex1 = mnm.VGroup(mnm.Tex('n! =',
                                  isolate=['n','!','='],
                                  font_size=112),
                          mnm.Tex(r'n \times (n-1) \times \cdots \times 1',
                                  isolate=['(','n-1',')','n',r'\times','1',r'\cdots'],
                                  font_size=112))
        tex1.arrange(mnm.RIGHT)
        tex1.set_color('#cfa3d3')
        
        tex2 = mnm.VGroup(mnm.Tex('6! =',
                                  isolate=['6','!','='],
                                  font_size=112),
                          mnm.Tex(r'6 \times 5 \times 4 \times 3 \times 2 \times 1',
                                  isolate=['6','5','4','3','2','1',r'\times'],
                                  font_size=112))
        tex2.arrange(mnm.RIGHT)
        tex2.set_color('#d0f2a9')
        
        tex3 = mnm.VGroup(mnm.Tex('4! =',
                                  isolate=['4','!','='],
                                  font_size=112),
                          mnm.Tex(r'4 \times 3 \times 2 \times 1',
                                  isolate=['4','3','2','1',r'\times'],
                                  font_size=112))
        tex3.arrange(mnm.RIGHT)
        tex3.set_color('#d1d3a3')
        
        vg = mnm.VGroup(tex1,tex3,tex2)
        vg.arrange(mnm.DOWN,buff=1)
        
        self.add(tex1)
        
        self.play(
            mnm.TransformMatchingTex(tex1[0].copy(),
                                     tex2[0],
                                     key_map={'n':'6'}),
            mnm.TransformMatchingTex(tex1[1].copy(),
                                     tex2[1],
                                     key_map={'n':'6','n-1':'5'}),
            run_time=1.25
        )
        
        self.play(
            mnm.TransformMatchingTex(tex1[0].copy(),
                                     tex3[0],
                                     key_map={'n':'4'}),
            mnm.TransformMatchingTex(tex1[1].copy(),
                                     tex3[1],
                                     key_map={'n':'4','n-1':'3'}),
            run_time=1.25
        )






TexText

คลาส TexText ใช้เขียนข้อความพร้อมกับแทรกสูตรสมการทางคณิตศาสตร์ไปด้วย

เมื่อเทียบกับคลาส Text แล้ว ข้อได้เปรียบคือพิมพ์พวกสมการใส่ลงไปได้ แต่ข้อเสียคือวัตถุที่ได้จะมีลักษณะเหมือน Tex คือสร้างช้าและไม่สามารถใช้ภาษาไทยได้

ส่วนที่ต้องการใช้เป็นสูตรสมการทางคณิตศาสตร์ให้ล้อมด้วย $ $ ส่วนตัวหนังสือที่เป็นข้อความธรรมดาไม่ต้องใส่ $ $

ตัวอย่าง ลองเขียนฟังก์ชันการแจกแจงแบบเรขาคณิต และ การแจกแจงแบบทวินามเชิงลบ
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        tt = mnm.TexText(r'''Geometrinen jakauma \\
                             $p(1-p)^{x-1}$ \\
                             Negatiivinen binomijakauma \\
                             $C(x+r-1,x)(1-p)^rp^x$''',
                         color='#f2a9e0',
                         font_size=100)
        self.play(
            mnm.Write(tt),
            run_time=1.25
        )
        self.wait(0.25)






Matrix

คลาส DecimalMatrix กับ IntegerMatrix ใช้สร้างเมทริกซ์ขึ้นมา โดย DecimalMatrix จะแสดงค่าเป็นเลขทศนิยม ส่วน IntegerMatrix จะแสดงค่าเป็นจำนวนเต็ม

ตัวอย่างการใช้
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        # ค่าในเมทริกซ์
        matr = [[1.2,2.4,3.6],
                [5,6.6,8.8]]
        # เมทริกซ์เลขทศนิยม
        matr1 = mnm.DecimalMatrix(matr)
        matr1.set_color('#ddf2a9')
        matr1.set_width(12)
        # เมทริกซ์จำนวนเต็ม
        matr2 = mnm.IntegerMatrix(matr)
        matr2.set_color('#caa9f2')
        matr2.set_width(12)
        
        self.play(
            mnm.Transform(matr1,matr2),
            run_time=1.25
        )
        self.wait(0.25)






Brace

คลาส Brace ใช้สร้างปีกกาคร่อมวัตถุที่ต้องการ มักใช้เพื่อการอธิบายตัววัตถุนั้นๆ

การใช้ให้ใส่วัตถุที่ต้องการคร่อม แล้วก็ตามด้วยทิศที่จะวาง

ตัวอย่างเช่นลองสร้างปีกกาแล้วแสดงข้อความอธิบายส่วนจริงส่วนจินตภาพของจำนวนเชิงซ้อน
import manimlib as mnm

class Manimala(mnm.Scene):
    def construct(self):
        tex = mnm.Tex('7-x','+','3i+yi',font_size=180,color="#e3ebb8")
        
        brace1 = mnm.Brace(tex[0],mnm.UP,color='#a3d3ba')
        text1 = mnm.Text('ส่วนจริง',color='#a3d3ba',size=1.6)
        text1.next_to(brace1,mnm.UP)
        
        brace2 = mnm.Brace(tex[2],mnm.UP,color='#d3a3bf')
        text2 = mnm.Text('ส่วนจินตภาพ',color='#d3a3bf',size=1.6)
        text2.next_to(brace2,mnm.UP)
        
        brace3 = mnm.Brace(tex,mnm.DOWN,color='#b8e3eb')
        text3 = mnm.Text('จำนวนเชิงซ้อน',color='#b8e3eb',size=2)
        text3.next_to(brace3,mnm.DOWN)
        
        self.add(tex)
        self.play(
            mnm.ClockwiseTransform(tex[0].copy(),brace1),
            mnm.CounterclockwiseTransform(tex[2].copy(),brace2),
            mnm.FadeTransform(tex.copy(),brace3),
            mnm.ShowCreation(text1),
            mnm.Write(text2),
            mnm.GrowFromCenter(text3),
            run_time=1.1
        )
        self.wait(0.4)





อ่านบทถัดไป >> บทที่ ๑๘





-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> manim

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文