Phương pháp xoắn ốc vàng
Bạn nói rằng bạn không thể làm cho phương pháp xoắn ốc vàng hoạt động và điều đó thật đáng tiếc vì nó thực sự rất tốt. Tôi muốn cung cấp cho bạn một sự hiểu biết đầy đủ về nó để có thể bạn có thể hiểu cách giữ cho điều này không bị “bó gọn”.
Vì vậy, đây là một cách nhanh chóng, không ngẫu nhiên để tạo một mạng tinh thể gần đúng; như đã thảo luận ở trên, không có mạng tinh thể nào là hoàn hảo, nhưng điều này có thể đủ tốt. Nó được so sánh với các phương pháp khác, ví dụ tại BendWavy.org nhưng nó chỉ có giao diện đẹp và đẹp cũng như đảm bảo về khoảng cách đều trong giới hạn.
Lớp lót: xoắn ốc hướng dương trên đĩa đơn vị
Để hiểu thuật toán này, trước tiên tôi mời các bạn xem qua thuật toán xoắn ốc 2D hướng dương. Điều này dựa trên thực tế rằng số vô tỷ nhất là tỷ lệ vàng (1 + sqrt(5))/2
và nếu một người phát ra điểm theo cách tiếp cận "đứng ở trung tâm, quay một tỷ lệ vàng của toàn bộ lượt, sau đó phát ra điểm khác theo hướng đó", người ta tự nhiên tạo ra một xoắn ốc, khi bạn đạt đến số điểm ngày càng cao, tuy nhiên, từ chối có các 'vạch' được xác định rõ ràng mà các điểm thẳng hàng. (Lưu ý 1.)
Thuật toán cho khoảng cách đều trên đĩa là,
from numpy import pi, cos, sin, sqrt, arange
import matplotlib.pyplot as pp
num_pts = 100
indices = arange(0, num_pts, dtype=float) + 0.5
r = sqrt(indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
pp.scatter(r*cos(theta), r*sin(theta))
pp.show()
và nó tạo ra kết quả giống như (n = 100 và n = 1000):
Giãn cách các điểm một cách xuyên tâm
Điều kỳ lạ chính là công thức r = sqrt(indices / num_pts)
; làm thế nào tôi đến với cái đó? (Lưu ý 2.)
Tôi đang sử dụng căn bậc hai ở đây vì tôi muốn chúng có khoảng cách diện tích bằng nhau xung quanh đĩa. Điều đó cũng giống như nói rằng trong giới hạn của N lớn, tôi muốn một vùng nhỏ R ∈ ( r , r + d r ), Θ ∈ ( θ , θ + d θ ) chứa một số điểm tỷ lệ với diện tích của nó, là r d r d θ . Bây giờ nếu chúng ta giả vờ rằng chúng ta đang nói về một biến ngẫu nhiên ở đây, thì điều này có một cách hiểu đơn giản là nói rằng mật độ xác suất chung cho ( R , Θ ) chỉ là cr đối với một số hằng sốc . Chuẩn hóa trên đĩa đơn vị sau đó sẽ buộc c = 1 / π.
Bây giờ hãy để tôi giới thiệu một thủ thuật. Nó xuất phát từ lý thuyết xác suất, nơi nó được gọi là lấy mẫu CDF nghịch đảo : giả sử bạn muốn tạo một biến ngẫu nhiên với mật độ xác suất f ( z ) và bạn có một biến ngẫu nhiên U ~ Uniform (0, 1), giống như đi ra từ random()
trong hầu hết các ngôn ngữ lập trình. Làm thế nào để bạn làm điều này?
- Đầu tiên, hãy biến mật độ của bạn thành một hàm phân phối tích lũy hoặc CDF, chúng ta sẽ gọi là F ( z ). Hãy nhớ rằng một CDF tăng đơn điệu từ 0 đến 1 với đạo hàm f ( z ).
- Sau đó tính hàm ngược F -1 ( z ) của CDF .
- Bạn sẽ thấy rằng Z = F -1 ( U ) được phân phối theo mật độ mục tiêu. (Chú thích 3).
Bây giờ, mẹo xoắn ốc tỷ lệ vàng sẽ tạo khoảng trống cho các điểm chỉ ra trong một mẫu đồng đều độc đáo cho θ, vì vậy hãy tích hợp điều đó ra; đối với đĩa đơn vị chúng ta còn lại với F ( r ) = r 2 . Vì vậy, hàm ngược là F -1 ( u ) = u 1/2 , và do đó chúng ta sẽ tạo ra các điểm ngẫu nhiên trên đĩa theo tọa độ cực với r = sqrt(random()); theta = 2 * pi * random()
.
Bây giờ thay vì lấy mẫu ngẫu nhiên hàm nghịch đảo này, chúng tôi lấy mẫu đồng nhất và điều tốt đẹp về lấy mẫu đồng nhất là kết quả của chúng tôi về cách các điểm được trải ra trong giới hạn N lớn sẽ hoạt động như thể chúng tôi đã lấy mẫu ngẫu nhiên. Sự kết hợp này là thủ thuật. Thay vì random()
chúng tôi sử dụng (arange(0, num_pts, dtype=float) + 0.5)/num_pts
, ví dụ, nếu chúng tôi muốn lấy mẫu 10 điểm r = 0.05, 0.15, 0.25, ... 0.95
. Chúng tôi lấy mẫu r một cách thống nhất để có được khoảng cách giữa các vùng bằng nhau và chúng tôi sử dụng gia số hướng dương để tránh các “vạch” điểm khủng khiếp trong đầu ra.
Bây giờ làm hoa hướng dương trên một quả cầu
Những thay đổi mà chúng ta cần thực hiện để chấm hình cầu bằng các điểm chỉ liên quan đến việc chuyển đổi tọa độ cực cho tọa độ cầu. Tất nhiên, tọa độ xuyên tâm không đi vào điều này bởi vì chúng ta đang ở trên một hình cầu đơn vị. Để giữ cho mọi thứ nhất quán hơn một chút ở đây, mặc dù tôi được đào tạo như một nhà vật lý, tôi sẽ sử dụng tọa độ của các nhà toán học trong đó 0 ≤ φ ≤ π là vĩ độ đi xuống từ cực và 0 ≤ θ ≤ 2π là kinh độ. Vì vậy, sự khác biệt so với ở trên là về cơ bản chúng ta đang thay thế biến r bằng φ .
Phần tử diện tích của chúng ta, trước đây là r d r d θ , bây giờ trở thành sin ( φ ) d φ d θ không phức tạp hơn nhiều . Vì vậy, mật độ khớp của chúng ta đối với khoảng cách đều là sin ( φ ) / 4π. Tích phân ra θ , ta tìm được f ( φ ) = sin ( φ ) / 2, do đó F ( φ ) = (1 - cos ( φ )) / 2. Đảo ngược điều này, chúng ta có thể thấy rằng một biến ngẫu nhiên đồng nhất sẽ giống như acos (1 - 2 u ), nhưng chúng ta lấy mẫu đồng nhất thay vì ngẫu nhiên, vì vậy thay vào đó chúng ta sử dụng φ k = acos (1 - 2 ( k+ 0,5) / N ). Và phần còn lại của thuật toán chỉ là chiếu điều này lên các tọa độ x, y và z:
from numpy import pi, cos, sin, arccos, arange
import mpl_toolkits.mplot3d
import matplotlib.pyplot as pp
num_pts = 1000
indices = arange(0, num_pts, dtype=float) + 0.5
phi = arccos(1 - 2*indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
x, y, z = cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi);
pp.figure().add_subplot(111, projection='3d').scatter(x, y, z);
pp.show()
Một lần nữa cho n = 100 và n = 1000, kết quả giống như sau:
Nghiên cứu thêm
Tôi muốn gửi lời cảm ơn đến blog của Martin Roberts. Lưu ý rằng ở trên, tôi đã tạo phần bù cho các chỉ số của mình bằng cách thêm 0,5 vào mỗi chỉ mục. Điều này chỉ hấp dẫn về mặt thị giác đối với tôi, nhưng hóa ra việc lựa chọn độ lệch đóng vai trò rất nhiều và không cố định trong khoảng thời gian và có thể có nghĩa là có được độ chính xác tốt hơn 8% trong việc đóng gói nếu được chọn đúng. Cũng cần phải có một cách để chuỗi R 2 của anh ta bao phủ một quả cầu và sẽ rất thú vị nếu điều này cũng tạo ra một lớp phủ đồng đều đẹp mắt, có lẽ là như vậy nhưng có lẽ cần phải được lấy từ một nửa của hình vuông đơn vị được cắt theo đường chéo hoặc dài hơn và kéo dài xung quanh để được một hình tròn.
Ghi chú
Các "vạch" đó được hình thành bởi các xấp xỉ hữu tỉ đối với một số và các xấp xỉ hữu tỉ tốt nhất đối với một số đến từ biểu thức phân số liên tục của nó, z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
trong đó z
là một số nguyên và n_1, n_2, n_3, ...
là một dãy số nguyên dương hữu hạn hoặc vô hạn:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
Vì phần của phân số 1/(...)
luôn nằm trong khoảng từ 0 đến 1, nên một số nguyên lớn trong phân số tiếp tục cho phép xấp xỉ hữu tỉ đặc biệt tốt: "một chia cho một số từ 100 đến 101" thì tốt hơn "một chia cho một số nào đó từ 1 đến 2" Do đó, số vô tỷ nhất là số có 1 + 1/(1 + 1/(1 + ...))
và không có xấp xỉ hữu tỷ đặc biệt tốt; người ta có thể giải φ = 1 + 1 / φ bằng cách nhân với φ để được công thức của tỉ lệ vàng.
Đối với những người không quá quen thuộc với NumPy - tất cả các chức năng đều được "vectơ hóa", vì vậy điều đó sqrt(array)
giống với những gì các ngôn ngữ khác có thể viết map(sqrt, array)
. Vì vậy, đây là một sqrt
ứng dụng từng thành phần . Điều tương tự cũng xảy ra đối với phép chia cho một đại lượng vô hướng hoặc phép cộng với đại lượng vô hướng - những điều này áp dụng cho tất cả các thành phần song song.
Việc chứng minh rất đơn giản khi bạn biết rằng đây là kết quả. Nếu bạn hỏi xác suất z < Z < z + d z là bao nhiêu , thì điều này cũng giống như hỏi xác suất z < F -1 ( U ) < z + d z là bao nhiêu , áp dụng F cho cả ba biểu thức, lưu ý rằng nó là một hàm tăng đơn điệu, do đó F ( z ) < U < F ( z + d z ), hãy mở rộng vế phải để tìm F ( z ) + f(z ) d z , và vì U là đồng nhất nên xác suất chỉ là f ( z ) d z như đã hứa.