Những lợi ích và hạn chế vốn có của việc sử dụng các lớp để đóng gói các thuật toán số là gì?


13

Nhiều thuật toán được sử dụng trong điện toán khoa học có cấu trúc vốn có khác với các thuật toán thường được xem xét trong các hình thức kỹ thuật phần mềm ít chuyên sâu về toán học. Cụ thể, các thuật toán toán học riêng lẻ có xu hướng rất phức tạp, thường liên quan đến hàng trăm hoặc hàng nghìn dòng mã, tuy nhiên không liên quan đến trạng thái (nghĩa là không hoạt động theo cấu trúc dữ liệu phức tạp) và thường có thể được đun sôi - về mặt lập trình giao diện - đến một chức năng duy nhất hoạt động trên một mảng (hoặc hai).

Điều này cho thấy rằng một hàm, chứ không phải một lớp, là giao diện tự nhiên cho hầu hết các thuật toán gặp phải trong tính toán khoa học. Tuy nhiên, lập luận này cung cấp một cái nhìn sâu sắc về cách thực hiện các thuật toán phức tạp, đa phần nên được xử lý.

Mặc dù cách tiếp cận truyền thống chỉ đơn giản là có một hàm gọi một số hàm khác, chuyển các đối số có liên quan trên đường đi, OOP cung cấp một cách tiếp cận khác, trong đó các thuật toán có thể được gói gọn thành các lớp. Để rõ ràng, bằng cách gói gọn một thuật toán trong một lớp, tôi có nghĩa là tạo một lớp trong đó các đầu vào thuật toán được nhập vào hàm tạo của lớp, và sau đó một phương thức công khai được gọi để thực sự gọi thuật toán. Việc triển khai multigrid trong C ++ psuedocode có thể giống như:

class multigrid {
    private:
        x_, b_
        [grid structure]

        restrict(...)
        interpolate(...)
        relax(...)
    public:
        multigrid(x,b) : x_(x), b_(b) { }
        run()
}

multigrid::run() {
     [call restrict, interpolate, relax, etc.]
}

Câu hỏi của tôi là như sau: những lợi ích và hạn chế của loại thực hành này so với cách tiếp cận truyền thống hơn mà không có lớp học là gì? Có vấn đề về khả năng mở rộng hoặc bảo trì? Để rõ ràng, tôi không có ý định thu hút ý kiến, mà là để hiểu rõ hơn về các hiệu ứng xuôi dòng (tức là những hiệu ứng có thể không phát sinh cho đến khi một cơ sở mã hóa trở nên khá lớn) khi áp dụng một thực hành mã hóa như vậy.


2
Đó luôn là một dấu hiệu xấu khi tên lớp của bạn là một tính từ chứ không phải là một danh từ.
David Ketcheson

3
Một lớp có thể đóng vai trò là không gian tên không trạng thái để tổ chức các hàm nhằm quản lý độ phức tạp, nhưng có nhiều cách khác để quản lý độ phức tạp trong các ngôn ngữ cung cấp các lớp. (Không gian tên trong C ++ và các mô-đun trong Python xuất hiện trong tâm trí.)
Geoff Oxberry

@GeoffOxberry Tôi không thể nói liệu đây là cách sử dụng tốt hay xấu - đó là lý do tại sao tôi hỏi ngay từ đầu - nhưng các lớp, không giống như không gian tên hoặc mô-đun, cũng có thể quản lý "trạng thái tạm thời", ví dụ: phân cấp lưới trong multigrid, được loại bỏ sau khi hoàn thành thuật toán.
Ben

Câu trả lời:


13

Đã làm phần mềm số trong 15 năm, tôi có thể tuyên bố rõ ràng như sau:

  • Đóng gói là quan trọng. Bạn không muốn chuyển xung quanh con trỏ tới dữ liệu (như bạn đề xuất) vì nó hiển thị sơ đồ lưu trữ dữ liệu. Nếu bạn trưng bày sơ đồ lưu trữ, bạn không bao giờ có thể thay đổi lại vì bạn sẽ truy cập dữ liệu trên toàn bộ chương trình. Cách duy nhất để tránh điều này là đóng gói dữ liệu vào các biến thành viên riêng của một lớp và chỉ để các hàm thành viên hành động trên nó. Nếu tôi đọc câu hỏi của bạn, bạn nghĩ về một hàm tính giá trị riêng của ma trận là không trạng thái, lấy một con trỏ đến các mục ma trận làm đối số và trả về giá trị riêng theo một cách nào đó. Tôi nghĩ rằng đây là cách sai lầm để suy nghĩ về nó. Theo quan điểm của tôi, hàm này phải là một hàm thành viên "const" của một lớp - không phải vì nó thay đổi ma trận, mà bởi vì nó là hàm hoạt động với dữ liệu.

  • Hầu hết các ngôn ngữ lập trình OO cho phép bạn có các chức năng thành viên riêng. Đây là cách của bạn để tách một thuật toán lớn thành một thuật toán nhỏ hơn. Ví dụ, các hàm trợ giúp khác nhau mà bạn cần cho tính toán eigenvalue vẫn hoạt động trên ma trận và do đó, đương nhiên sẽ là các hàm thành viên riêng của một lớp ma trận.

  • So với nhiều hệ thống phần mềm khác, có thể đúng là hệ thống phân cấp lớp thường ít quan trọng hơn so với, trong các giao diện người dùng đồ họa. Chắc chắn có những vị trí trong phần mềm số nổi bật - Jed phác thảo một câu trả lời khác cho chủ đề này, cụ thể là nhiều cách người ta có thể biểu diễn một ma trận (hay nói chung hơn là toán tử tuyến tính trên không gian vectơ hữu hạn). PETSc thực hiện điều này rất nhất quán, với các chức năng ảo cho tất cả các hoạt động hoạt động trên ma trận (họ không gọi đó là "chức năng ảo", nhưng đó chính là nó). Có các lĩnh vực khác trong mã phần tử hữu hạn điển hình trong đó người ta sử dụng nguyên tắc thiết kế này của phần mềm OO. Những cái mà tôi nghĩ đến là nhiều loại công thức bậc hai và nhiều loại phần tử hữu hạn, tất cả đều được biểu diễn một cách tự nhiên như một giao diện / nhiều triển khai. Mô tả pháp luật vật chất cũng sẽ rơi vào nhóm này. Nhưng có thể đúng là về điều đó và phần còn lại của mã phần tử hữu hạn không sử dụng tính kế thừa một cách phổ biến như người ta có thể sử dụng nó, giả sử, trong GUI.

Chỉ từ ba điểm này, rõ ràng lập trình hướng đối tượng chắc chắn cũng có thể áp dụng được cho các mã số, và sẽ thật ngu ngốc nếu bỏ qua nhiều lợi ích của phong cách này. Có thể đúng là BLAS / LAPACK không sử dụng mô hình này (và giao diện thông thường được MATLAB tiết lộ cũng không) nhưng thực tế tôi sẽ đoán rằng mọi phần mềm số thành công được viết trong 10 năm qua thực tế là hướng đối tượng.


16

Đóng gói và ẩn dữ liệu là cực kỳ quan trọng đối với các thư viện mở rộng trong điện toán khoa học. Xem xét ma trận và bộ giải tuyến tính là hai ví dụ. Người dùng chỉ cần biết rằng một toán tử là tuyến tính, nhưng nó có thể có cấu trúc bên trong như độ thưa, hạt nhân, biểu diễn phân cấp, sản phẩm tenor hoặc bổ sung Schur. Trong mọi trường hợp, các phương thức Krylov không phụ thuộc vào chi tiết của toán tử, chúng chỉ phụ thuộc vào hành động của MatMulthàm (và có lẽ là sự điều chỉnh của nó). Tương tự, người dùng giao diện bộ giải tuyến tính (ví dụ: bộ giải phi tuyến) chỉ quan tâm rằng vấn đề tuyến tính đã được giải quyết và không cần hoặc không muốn chỉ định thuật toán được sử dụng. Thật vậy, việc chỉ định những thứ như vậy sẽ cản trở khả năng của bộ giải phi tuyến (hoặc giao diện bên ngoài khác).

Giao diện là tốt. Tùy thuộc vào một thực hiện là xấu. Cho dù bạn đạt được điều này bằng cách sử dụng các lớp C ++, đối tượng C, kiểu chữ Haskell hoặc một số tính năng ngôn ngữ khác là không quan trọng. Khả năng, sự mạnh mẽ và khả năng mở rộng của một giao diện là điều quan trọng trong các thư viện khoa học.


8

Các lớp chỉ nên được sử dụng nếu cấu trúc của mã được phân cấp. Vì bạn đang đề cập đến Thuật toán, cấu trúc tự nhiên của chúng là biểu đồ luồng, không phải là hệ thống phân cấp của các đối tượng.

Trong trường hợp của OpenFOAM, phần thuật toán được triển khai theo các toán tử chung (div, grad, curl, v.v.) về cơ bản là các hàm trừu tượng hoạt động trên các loại tenxơ khác nhau, sử dụng các kiểu sơ đồ số khác nhau. Phần mã này về cơ bản được xây dựng từ rất nhiều thuật toán chung hoạt động trên các lớp. Điều này cho phép khách hàng viết một cái gì đó như:

solve(ddt(U) + div(phi, U)  == rho*g + ...);

Các hệ thống phân cấp như mô hình vận chuyển, mô hình nhiễu loạn, sơ đồ khác nhau, sơ đồ độ dốc, điều kiện biên, v.v ... được thực hiện theo các lớp C ++ (một lần nữa, chung về số lượng tenxơ).

Tôi đã nhận thấy một cấu trúc tương tự trong thư viện CGAL, trong đó các thuật toán khác nhau được tập hợp lại thành các nhóm đối tượng hàm được đóng gói với thông tin hình học để tạo thành Kernels hình học (các lớp), nhưng điều này một lần nữa được thực hiện để tách các hoạt động khỏi hình học (loại bỏ điểm khỏi một khuôn mặt, từ một kiểu dữ liệu điểm).

Cấu trúc phân cấp ==> các lớp

Thủ tục, biểu đồ luồng ==> thuật toán


5

Ngay cả khi đây là một câu hỏi cũ, tôi nghĩ rằng nó đáng để đề cập đến giải pháp cụ thể của Julia . Ngôn ngữ này làm là "OOP không có lớp": các cấu trúc chính là các kiểu, tức là các đối tượng dữ liệu tổng hợp gần giống với structs trong C, trên đó xác định quan hệ thừa kế. Các loại không có "hàm thành viên", nhưng mỗi hàm có chữ ký loại và chấp nhận các kiểu con. Ví dụ, bạn có thể có một bản tóm tắt Matrixloại và phân nhóm DenseMatrix, SparseMatrixvà có một phương pháp chung do_something(a::Matrix, b::Matrix)với chuyên môn hóa do_something(a::SparseMatrix, b::SparseMatrix). Nhiều công văn được sử dụng để chọn phiên bản phù hợp nhất để gọi.

Cách tiếp cận này mạnh hơn OOP dựa trên lớp, tương đương với việc gửi đi chỉ dựa trên tính kế thừa, nếu bạn chấp nhận quy ước rằng "một phương thức là một hàm với thistham số đầu tiên" (ví dụ phổ biến trong Python). Một số hình thức của nhiều công văn có thể được mô phỏng trong C ++, nhưng với những mâu thuẫn đáng kể .

Sự khác biệt chính là các phương thức không thuộc về các lớp, nhưng chúng tồn tại như các thực thể riêng biệt và sự kế thừa có thể xảy ra trên tất cả các tham số.

Một số tài liệu tham khảo:

http://docs.julialang.org/en/release-0.4/manual/methods/

http://assoc.tumblr.com/post/71454527084/cool-things-you-can-do-in-julia

https://thenewphalls.wordpress.com/2014/03/06/under Hiểu -object-oriented-program-in-julia-đầu mối-part-2 /


1

Hai ưu điểm của phương pháp OO có thể là:

  • βαcalculate_alpha()αcalculate_beta()calculate_alpha()α

  • một phép tính với một vài đầu vào, trong đó nếu một đầu vào thay đổi thì toàn bộ phép tính không nhất thiết phải được thực hiện. Ví dụ, calculate_f()phương thức trả về . Nếu sau đó bạn quyết định làm lại phép tính cho giá trị khác của z , bạn có thể gọi vàf(x,y,z)zset_z()zcalculate_f()z

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.