สำรวจความงามอันไม่สิ้นสุดของ fractal ผ่านการเขียนโปรแกรม — บทเรียนเชิงโต้ตอบพร้อมภาพจำลองที่ปรับค่าได้
Fractal คือรูปทรงทางคณิตศาสตร์ที่มีคุณสมบัติ self-similarity — หมายความว่าไม่ว่าจะ zoom เข้าไปมากเท่าไร รูปแบบที่เห็นจะคล้ายคลึงกับโครงสร้างทั้งหมดเสมอ แนวคิดนี้ถูกนำเสนอโดย Benoît Mandelbrot ในทศวรรษ 1970 และเปลี่ยนแปลงวิธีที่เราเข้าใจรูปทรงในธรรมชาติ
ในธรรมชาติเราพบแฟร็กทัลอยู่ทุกหนทุกแห่ง — จากกิ่งก้านของต้นไม้ เส้นเลือดในร่างกาย แนวชายฝั่งทะเล ไปจนถึงเกล็ดหิมะ ทั้งหมดนี้ล้วนมีโครงสร้างที่ซ้ำตัวเอง (recursion) ในระดับที่ต่างกัน
ภาษา Python เป็นเครื่องมือที่เหมาะสมอย่างยิ่ง สำหรับการสร้างและสำรวจแฟร็กทัล เพราะมีไลบรารีจำนวนมากสำหรับการคำนวณตัวเลข (numerical computation) และการสร้างภาพ (visualization)
Mandelbrot set เป็นชุดของ complex number c ที่ทำให้ฟังก์ชัน zn+1 = zn² + c ไม่ diverge เมื่อทำ iteration ซ้ำไปเรื่อย ๆ โดยเริ่มจาก z₀ = 0
ถ้าหลังจากทำซ้ำหลายรอบแล้วค่าสัมบูรณ์ของ z ยังไม่เกิน 2 เราจะถือว่าจุดนั้น converge และเป็นส่วนหนึ่งของเซต สีที่ใช้แสดงผลจะขึ้นอยู่กับจำนวนรอบที่ค่า diverge ออกไป
Julia set ใช้สมการเดียวกัน (zn+1 = zn² + c) แต่ต่างกันตรงที่ค่า c จะคงที่ (constant) ขณะที่จุดเริ่มต้น z₀ จะเปลี่ยนไปตามตำแหน่งบนระนาบ ค่า c แต่ละค่าจะให้ Julia set ที่มีรูปร่างแตกต่างกัน
Sierpinski triangle เป็นแฟร็กทัลที่สร้างจาก recursion — เริ่มจากสามเหลี่ยมใหญ่ แล้วตัดสามเหลี่ยมตรงกลางออก จากนั้นทำซ้ำกับสามเหลี่ยมย่อยทุกชิ้น ทุกระดับของ iteration จะเพิ่มรายละเอียดมากขึ้น
ก่อนที่ Mandelbrot จะบัญญัติคำว่า "แฟร็กทัล" ราว 500 ปี เลโอนาร์โด ดา วินชี ได้บันทึกการสังเกตใน Paris Manuscript M (folio 78v) ซึ่งเป็นสมุดบันทึกขนาดเล็กที่นโปเลียนยึดจากมิลานไปปารีส ว่าเมื่อกิ่งไม้แตกสาขา พื้นที่หน้าตัดรวมของกิ่งย่อยจะเท่ากับพื้นที่หน้าตัดของกิ่งหลัก:
กฎนี้เรียกว่า da Vinci rule — สร้างโครงสร้าง self-similar ที่รูปแบบการแตกสาขาซ้ำกันทุกระดับตั้งแต่ลำต้นจนถึงกิ่งเล็กที่สุด งานวิจัยของ Christophe Eloy (2011, Physical Review Letters) ยืนยันว่ากฎนี้ช่วยให้ต้นไม้ ต้านทานแรงลมได้ดีที่สุด ดา วินชีใช้เลขชี้กำลัง 2 (อนุรักษ์พื้นที่) แต่ต้นไม้จริงมีค่าระหว่าง 1.8 ถึง 2.3 ตามชนิดพันธุ์ — ใกล้เคียงมากสำหรับการสังเกตเมื่อ 500 ปีก่อน!
นอกจากนี้ ดา วินชียังวาดภาพ turbulence ของน้ำ (ค.ศ. 1506–1513, Royal Collection ที่ Windsor) แสดงกระแสน้ำวนซ้อนกระแสน้ำวน — สิ่งที่เราเข้าใจในปัจจุบันว่าเป็น turbulent cascade ซึ่งเป็นตัวอย่างสำคัญของโครงสร้างแฟร็กทัลในฟิสิกส์
Flower of Life คือรูปแบบทางเรขาคณิตศักดิ์สิทธิ์ ที่ประกอบด้วยวงกลมซ้อนทับกันเป็นจำนวนมาก สร้างโครงสร้างหกเหลี่ยมที่ self-similar — ทุกจุดตัดสามารถเป็นศูนย์กลางของ Flower of Life ชุดใหม่ที่มีรูปแบบเดียวกัน
พบลวดลายนี้วาดด้วยสีแดงเข้ม (red ochre) บนผนังหิน Osireion ในวิหาร Seti I ที่ Abydos ประเทศอียิปต์ — มักอ้างว่าอายุ 6,000 ปี แต่นักวิชาการระบุว่าเป็นกราฟฟิตี้ จากยุคกรีก-โรมัน (ศตวรรษที่ 1 ก่อนคริสตกาลหรือหลังกว่านั้น) ตัวอาคาร Osireion เก่าแก่จริง (สร้างราว 1280 ปีก่อนคริสตกาล) แต่ลวดลายถูกเพิ่มทีหลัง — อย่าตัดสินอายุกราฟฟิตี้จากอายุกำแพง! อย่างไรก็ตาม รูปแบบนี้ปรากฏในศิลปะของจีน ญี่ปุ่น อินเดีย และยุโรปยุคกลาง ภายในรูปแบบนี้ซ่อนอยู่ด้วยรูปทรงทางคณิตศาสตร์สำคัญ เช่น Metatron's Cube ซึ่งบรรจุ Platonic solids ทั้ง 5 รูปทรง
Fibonacci sequence (1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...) ถูกอธิบายในโลกตะวันตกโดย Leonardo of Pisa ใน Liber Abaci (ค.ศ. 1202) แต่ Pingala นักฉันทศาสตร์อินเดีย (ราวศตวรรษที่ 3 ก่อนคริสตกาล) ได้ซ่อนแนวคิดนี้ไว้ในสูตรลึกลับ misrau cha ("ทั้งสองปะปนกัน") ในตำรา Chandahshastra เกี่ยวกับจังหวะในบทกวีสันสกฤต ต่อมา Virahanka (ราว ค.ศ. 700) เขียนสูตรอย่างชัดเจน และ Hemachandra (ค.ศ. 1150) อธิบายเพิ่มเติม — ทั้งหมดก่อน Fibonacci หลายศตวรรษ
อัตราส่วนของตัวเลขที่อยู่ติดกันลู่เข้าสู่ golden ratio φ ≈ 1.618 — ซึ่ง φ นิยามตัวเองแบบ recursive: φ = 1 + 1/φ เหมือนแฟร็กทัลที่นิยามตัวเองซ้ำไปไม่สิ้นสุด
ในธรรมชาติ จำนวนเกลียวในดอกทานตะวัน สับปะรด และลูกสน มักเป็นเลขฟีโบนัชชีติดกัน (เช่น 34 กับ 55) เพราะการจัดเรียงที่มุมทอง (≈137.5°) ให้ความหนาแน่นสูงสุด — ยืนยันโดยงานวิจัยของ Douady & Couder (1992, Physical Review Letters)
ฟีโบนัชชีในเซตแมนเดลบรอท: Robert Devaney (Boston University) แสดงให้เห็นว่า "หัว" (bulb) ที่ติดกับรูปหัวใจหลัก ของ Mandelbrot set มีคาบที่เรียงตามลำดับฟีโบนัชชี — เช่น ระหว่าง bulb 1/2 และ 1/3 จะพบ 2/5, 3/8, 5/13, 8/21 ซึ่งล้วนเป็นอัตราส่วนฟีโบนัชชี
แนวคิดเรื่อง self-similarity และ recursion ไม่ได้เป็นเรื่องใหม่ของคณิตศาสตร์สมัยใหม่ — ปรัชญาตะวันออกพูดถึงแนวคิดเหล่านี้มาหลายพันปีแล้ว:
ตาข่ายอันไม่มีที่สิ้นสุดที่ทุกจุดตัดมีอัญมณีสะท้อนอัญมณีทุกดวงอื่น และในภาพสะท้อนแต่ละภาพก็มีภาพสะท้อนของทั้งหมดอีกที — ซ้ำไปไม่สิ้นสุด นี่คือคำอธิบาย self-similarity ที่สมบูรณ์แบบ: ส่วนหนึ่งบรรจุทั้งหมดไว้ในตัว
พระอาจารย์ Fazang (ค.ศ. 643–712) สาธิตแนวคิดนี้ให้จักรพรรดินีบูเช็กเทียน (สตรีเพียงคนเดียวที่ครองราชย์เป็นจักรพรรดิจีน) ทรงเห็นด้วยพระเนตร — สร้างห้องกระจกรอบด้านพร้อมพระพุทธรูปและเทียน ให้เกิดการสะท้อนซ้อนสะท้อนไม่มีที่สิ้นสุด นี่คือ "Infinity Room" แห่งแรกของโลก ก่อน Yayoi Kusama ราว 1,300 ปี!
Sri Yantra ประกอบด้วย 9 สามเหลี่ยมซ้อนกัน สร้างสามเหลี่ยมย่อย 43 รูปในโครงสร้างที่ self-similar — คล้ายกับ Sierpinski triangle ทางคณิตศาสตร์ มณฑลทิเบตก็มีโครงสร้างซ้ำตัวเองจากขอบนอกเข้าสู่ศูนย์กลาง — เหมือนการ zoom เข้าไปในแฟร็กทัล
วัฏจักรเวลาของฮินดูซ้อนกันเป็นชั้น ๆ: ยุค (432,000 ปี) → มหายุค (4 ยุค = 4.32 ล้านปี) → มนวันตร (71 มหายุค) → กัลป์ (14 มนวันตร = 4.32 พันล้านปี) → อายุของพรหม (311.04 ล้านล้านปี) — วงจรซ้อนวงจร เหมือน fractal ในมิติเวลา
เต๋าเต๋อจิง บทที่ 42: "เต๋าให้กำเนิดหนึ่ง หนึ่งให้กำเนิดสอง สองให้กำเนิดสาม สามให้กำเนิดสรรพสิ่ง" — นี่คือคำอธิบาย recursive generation จากกฎง่าย ๆ ซึ่งเป็นหัวใจของแฟร็กทัล: สมการ z → z² + c ก็สร้างความซับซ้อนอนันต์จากกฎสั้น ๆ เพียงบรรทัดเดียว
ลายกระหนก ลวดลายเปลวไฟอันเป็นเอกลักษณ์ของศิลปะไทย มีโครงสร้างที่เป็นแฟร็กทัลอย่างแท้จริง — ลายกระหนกใหญ่ประกอบด้วยลายกระหนกเล็กซ้อนอยู่ภายใน ซ้ำหลายระดับ ดูได้จากลวดลายที่วัดพระแก้วและพระบรมมหาราชวัง รูปแบบ self-similar นี้ซ้อนกันอย่างน้อย 3–4 ระดับในงานประดับเดียว
สุนทรียศาสตร์ wabi-sabi ของญี่ปุ่น (รากฐานจากเซนพุทธ) ให้คุณค่ากับความไม่สมบูรณ์แบบตามธรรมชาติ — ผิวหยาบ รูปทรงอินทรีย์ รอยแตกร้าว — ซึ่งล้วนเป็นรูปทรงที่อธิบายด้วยเรขาคณิตแฟร็กทัล Kintsugi (金継ぎ) การซ่อมเครื่องปั้นด้วยทอง ก็เน้นย้ำรอยแตกที่เป็นแฟร็กทัล — การแตกร้าวในวัสดุเปราะสร้างกิ่งก้านที่มี fractal dimension วัดได้จริง ทองไม่เพียงซ่อมแซม แต่ลากเส้นตามเรขาคณิตของการทำลาย
ผู้ก่อตั้งสุนทรียศาสตร์นี้คือ เซน โนะ ริคิว ปรมาจารย์พิธีชา ผู้ถูกโชกุน Toyotomi Hideyoshi สั่งให้ทำเซ็ปปุกุ (ค.ศ. 1591) เพราะวางรูปปั้นตัวเองสวมรองเท้าไว้เหนือประตูที่โชกุนต้องเดินลอด — ก่อนตาย ท่านจัดพิธีชาครั้งสุดท้าย แจกอุปกรณ์ให้แขกทุกชิ้น ยกเว้นถ้วยชาที่ท่านทุบทำลายพร้อมสาปว่า "ถ้วยที่แตะริมฝีปากแห่งเคราะห์ร้ายจะไม่มีใครใช้อีก"
Sacred geometry คือการศึกษารูปแบบเรขาคณิตที่ปรากฏในธรรมชาติและถูกนำมาใช้ในสถาปัตยกรรมทางศาสนาทั่วโลก แฟร็กทัลให้กรอบทางคณิตศาสตร์ที่อธิบายว่าทำไมรูปแบบเหล่านี้จึงปรากฏซ้ำแล้วซ้ำเล่า:
ศิลปะเรขาคณิตอิสลาม — งานวิจัยของ Peter J. Lu และ Paul J. Steinhardt (2007, Science) แสดงให้เห็นว่าสถาปนิกมุสลิมยุคกลางสร้างลวดลาย quasi-crystalline (คล้าย Penrose tilings) ที่ self-similar ในมัสยิด Darb-i Imam (Isfahan, ค.ศ. 1453) — จากกระเบื้อง 3,700 ชิ้นที่วิเคราะห์ พบข้อผิดพลาดเพียง 11 จุด! สถาปนิกเหล่านี้สร้างลวดลาย quasi-crystal 500 ปีก่อนที่ Dan Shechtman จะค้นพบ quasi-crystal ในห้องปฏิบัติการ (ได้รับรางวัลโนเบลสาขาเคมี ปี 2011)
สถาปัตยกรรมวัดฮินดู — หอ shikhara ของวัด Kandariya Mahadeva (Khajuraho, ค.ศ. 1030) ล้อมรอบด้วยจำลองตัวเองขนาดเล็กกว่า ซ้ำ 3–4 ระดับ — งานวิจัยโดย Kirti Trivedi (1989, The Visual Computer)
แฟร็กทัลในแอฟริกา — Ron Eglash บันทึกใน African Fractals: Modern Computing and Indigenous Design (1999) ว่าหมู่บ้าน Ba-ila ในแซมเบียมีผังเป็นวงแหวนของบ้าน ที่แต่ละครอบครัวเป็นวงแหวนเล็กกว่าของโครงสร้าง — แฟร็กทัลซ้อนแฟร็กทัล
ก่อนเขียนโค้ด ต้องเข้าใจ complex number ก่อน — จำนวนที่มีรูปแบบ a + bi โดย a คือส่วนจริง (แกน x), b คือส่วนจินตภาพ (แกน y), และ i คือ √(-1) ทุกจุดบนภาพแฟร็กทัลคือจุดหนึ่งบนระนาบเชิงซ้อน
ค่าสัมบูรณ์ (absolute value) ของจำนวนเชิงซ้อน z = a + bi คือ |z| = √(a² + b²) — คือระยะห่างจากจุดกำเนิด เราใช้ค่านี้ตัดสินว่าจุดใด diverge: ถ้า |z| > 2 จุดนั้นจะหนีออกไปสู่อนันต์แน่นอน
คณิตศาสตร์: การยกกำลังสองของจำนวนเชิงซ้อน z = a + bi:
แล้วบวก c: znew = z² + c = (a² - b² + creal) + (2ab + cimag)i — นี่คือทั้งหมดที่โค้ดทำในแต่ละรอบ เพียงสองบรรทัดเลขคณิต แต่เมื่อทำซ้ำหลายล้านครั้ง ก็สร้างความซับซ้อนอนันต์ เหมือนที่เล่าจื่อกล่าว: "สามให้กำเนิดสรรพสิ่ง"
ด้านล่างคือโค้ดเต็มโดยใช้ NumPy และ Matplotlib — สร้างระนาบ 800×600 จุด แต่ละจุดคือค่า c หนึ่งค่า:
Python import numpy as np import matplotlib.pyplot as plt def mandelbrot(c, max_iter): """คำนวณจำนวนรอบก่อน diverge""" z = 0 for n in range(max_iter): if abs(z) > 2: return n z = z*z + c return max_iter # สร้างระนาบจำนวนเชิงซ้อน x = np.linspace(-2.5, 1.0, 800) y = np.linspace(-1.25, 1.25, 600) X, Y = np.meshgrid(x, y) C = X + 1j * Y # คำนวณแต่ละจุด max_iter = 100 img = np.vectorize(mandelbrot)(C, max_iter) # แสดงผล plt.figure(figsize=(12, 9)) plt.imshow(img, cmap='inferno', extent=[-2.5,1,-1.25,1.25]) plt.colorbar(label='จำนวนรอบ (iterations)') plt.title('Mandelbrot Set') plt.show()
ความแตกต่างทางคณิตศาสตร์: สมการ zn+1 = zn² + c เหมือนกันทุกประการ แต่:
ใน Mandelbrot: เริ่มจาก z₀ = 0 เสมอ และ c เปลี่ยนตามตำแหน่ง (คำถาม: "ค่า c ใดไม่ทำให้ลู่ออก?")
ใน Julia: c คงที่ค่าเดียว และ z₀ เปลี่ยนตามตำแหน่ง (คำถาม: "จุดเริ่มต้นใดไม่ทำให้ลู่ออก?")
ดังนั้นค่า c แต่ละค่าจะให้ Julia set รูปร่างต่างกัน — ถ้า c อยู่ ใน Mandelbrot set, Julia set จะเป็นผืนเดียวต่อเนื่อง แต่ถ้า c อยู่ นอก Mandelbrot set, Julia set จะแตกเป็น "ฝุ่น" (Fatou dust)
Python def julia(z, c, max_iter): """คำนวณ Julia set สำหรับค่า c ที่กำหนด""" for n in range(max_iter): if abs(z) > 2: return n z = z*z + c return max_iter # ค่า c ที่น่าสนใจ: # c = -0.7 + 0.27015j (รูปแบบคลาสสิก) # c = -0.8 + 0.156j (เกลียวงดงาม) # c = 0.355 + 0.355j (ดอกไม้) c = -0.7 + 0.27015j img = np.vectorize(lambda z: julia(z, c, 200))(C)
คณิตศาสตร์: ในแต่ละรอบ จำนวนสามเหลี่ยมเพิ่มเป็น 3 เท่า: 1 → 3 → 9 → 27 → 81 ... (3ⁿ ชิ้น ที่ความลึก n) ขณะที่พื้นที่แต่ละชิ้นลดลงเป็น ¼: พื้นที่รวม = (3/4)ⁿ ของสามเหลี่ยมเดิม เมื่อ n → ∞ พื้นที่ลู่เข้า 0 แต่จำนวนชิ้นเป็นอนันต์ — fractal dimension ของ Sierpinski triangle คือ log(3)/log(2) ≈ 1.585 ซึ่งอยู่ระหว่างเส้น (1 มิติ) กับพื้นผิว (2 มิติ)
Python import matplotlib.pyplot as plt import matplotlib.patches as patches def sierpinski(ax, vertices, depth): """วาดสามเหลี่ยมเซียร์พินสกีด้วย recursion""" if depth == 0: triangle = patches.Polygon(vertices, fill=True) ax.add_patch(triangle) return # หาจุดกึ่งกลางแต่ละด้าน mid = lambda a, b: ((a[0]+b[0])/2, (a[1]+b[1])/2) v0, v1, v2 = vertices sierpinski(ax, [v0, mid(v0,v1), mid(v0,v2)], depth-1) sierpinski(ax, [v1, mid(v0,v1), mid(v1,v2)], depth-1) sierpinski(ax, [v2, mid(v0,v2), mid(v1,v2)], depth-1)
ปัญหา: ถ้าใช้จำนวนรอบเป็นจำนวนเต็ม สีจะเป็นแถบ ๆ ชัดเจน แก้ไข: ใช้สูตร smooth coloring:
ทำไมต้อง log สองชั้น? เมื่อ z หนีออก |z| เติบโตแบบ |z|² ต่อรอบ ดังนั้น log|z| เติบโตแบบ 2ⁿ และ log(log|z|) เติบโตแบบ n — ให้ค่าต่อเนื่อง ที่เชื่อมระหว่างจำนวนเต็ม n และ n+1 ตามที่ |z| อยู่ระหว่างรอบ
Python — Smooth Coloring import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap def mandelbrot_smooth(c, max_iter): """Smooth coloring — เทคนิคเดียวกับ JS canvas""" z = 0 for n in range(max_iter): if abs(z) > 2: # ค่า smooth แทนจำนวนเต็ม smooth = n + 1 - np.log(np.log(abs(z))) / np.log(2) return smooth z = z*z + c return max_iter # สร้างชุดสีแบบ "cosmic" เหมือนใน JS def cosmic_palette(t): r = 9 * (1-t) * t**3 g = 15 * (1-t)**2 * t**2 b = 8.5 * (1-t)**3 * t return (r, g, b) # สร้าง colormap จากฟังก์ชัน colors = [cosmic_palette(t/255) for t in range(256)] cmap = LinearSegmentedColormap.from_list('cosmic', colors) img = np.vectorize(mandelbrot_smooth)(C, 200) plt.imshow(img, cmap=cmap)
หน้าเว็บนี้ใช้ Web Worker เพื่อเรนเดอร์แฟร็กทัล
โดยไม่บล็อก UI ใน Python ทำได้เช่นกันด้วย multiprocessing —
แบ่งภาพเป็นแถวแล้วคำนวณแต่ละส่วนพร้อมกัน:
Python — Parallel Rendering from multiprocessing import Pool import numpy as np def compute_row(args): """คำนวณแถวเดียว — ส่งไปทำงานใน process แยก""" y_val, x_arr, max_iter = args row = np.empty(len(x_arr)) for i, x_val in enumerate(x_arr): c = complex(x_val, y_val) z = 0 for n in range(max_iter): if abs(z) > 2: row[i] = n + 1 - np.log(np.log(abs(z))) / np.log(2) break z = z*z + c else: row[i] = max_iter return row x = np.linspace(-2.5, 1.0, 800) y = np.linspace(-1.25, 1.25, 600) # แบ่งงานเป็น 600 แถว ส่งไปคำนวณพร้อมกัน with Pool() as pool: args = [(yi, x, 200) for yi in y] rows = pool.map(compute_row, args) img = np.array(rows)
พื้นหลังของหน้านี้มีอนุภาคลอยตัวที่เชื่อมต่อกันเมื่ออยู่ใกล้ — สร้างเอฟเฟกต์เดียวกันใน Python ได้ด้วย Pygame:
Python — Particle System import pygame, random, math pygame.init() W, H = 800, 600 screen = pygame.display.set_mode((W, H)) class Particle: def __init__(self): self.x = random.uniform(0, W) self.y = random.uniform(0, H) self.vx = random.uniform(-0.3, 0.3) self.vy = random.uniform(-0.3, 0.3) self.r = random.uniform(1, 3) self.color = random.choice([ (108,92,231), (0,206,201) ]) particles = [Particle() for _ in range(60)] running = True while running: for e in pygame.event.get(): if e.type == pygame.QUIT: running = False screen.fill((6, 6, 14)) for p in particles: p.x = (p.x + p.vx) % W p.y = (p.y + p.vy) % H pygame.draw.circle(screen, p.color, (int(p.x), int(p.y)), int(p.r)) # เส้นเชื่อมอนุภาคที่อยู่ใกล้กัน for i, a in enumerate(particles): for b in particles[i+1:]: d = math.hypot(a.x-b.x, a.y-b.y) if d < 120: alpha = int(15 * (1 - d/120)) s = pygame.Surface((W,H), pygame.SRCALPHA) pygame.draw.line(s, (108,92,231,alpha), (int(a.x),int(a.y)), (int(b.x),int(b.y))) screen.blit(s, (0,0)) pygame.display.flip() pygame.time.Clock().tick(60)