Có thể tối ưu hóa mã tích hợp này để nó chạy nhanh hơn không?


9
double trap(double func(double), double b, double a, double N) {
  double j;
  double s;
  double h = (b-a)/(N-1.0); //Width of trapezia

  double func1 = func(a);
  double func2;

  for (s=0,j=a;j<b;j+=h){
    func2 = func(j+h);
    s = s + 0.5*(func1+func2)*h;
    func1 = func2;
  }

  return s;
}

Trên đây là mã C ++ của tôi để tích hợp số 1D (sử dụng quy tắc hình thang mở rộng) func()giữa các giới hạn bằng[a,b]N1 .

Tôi thực sự đang thực hiện tích hợp 3D, trong đó mã này được gọi là đệ quy. Tôi làm việc với N=50 cho tôi kết quả tốt.

Ngoài việc giảm N hơn nữa, có ai có thể đề xuất cách tối ưu hóa mã ở trên để nó chạy nhanh hơn không? Hoặc, thậm chí, có thể đề xuất một phương pháp tích hợp nhanh hơn?


5
Điều này không thực sự phù hợp với câu hỏi, nhưng tôi khuyên bạn nên chọn tên biến tốt hơn. Giống như trapezoidal_integrationthay vì trap, sumhoặc running_totalthay vì s(và cũng sử dụng +=thay vì s = s +), trapezoid_widthhoặc dxthay vì h(hoặc không, tùy thuộc vào ký hiệu ưa thích của bạn cho quy tắc hình thang), và thay đổi func1func2phản ánh thực tế rằng chúng là các giá trị, không phải là hàm. Ví dụ func1-> previous_valuefunc2-> current_value, hoặc một cái gì đó tương tự.
David Z

Câu trả lời:


5

Về mặt toán học, biểu thức của bạn tương đương với:

I=h(12f1+f2+f3+...+fn1+12fn)+O((ba)3fn2)

Vì vậy, bạn có thể thực hiện điều đó. Như đã nói, thời gian có lẽ bị chi phối bởi việc đánh giá chức năng, vì vậy để có được độ chính xác tương tự, bạn có thể sử dụng phương pháp tích hợp tốt hơn yêu cầu đánh giá chức năng ít hơn.

Hình cầu Gaussian, trong thời hiện đại, nhiều hơn một chút so với một món đồ chơi; chỉ hữu ích nếu bạn yêu cầu rất ít đánh giá. Nếu bạn muốn một cái gì đó dễ thực hiện, bạn có thể sử dụng quy tắc của Simpson, nhưng tôi sẽ không đi xa hơn là đặt hàng mà không có lý do chính đáng.1/N3

Nếu độ cong của hàm thay đổi nhiều, bạn có thể sử dụng thói quen bước thích ứng, sẽ chọn bước lớn hơn khi hàm phẳng và độ chính xác nhỏ hơn khi độ cong cao hơn.


Sau khi đi xa và trở lại vấn đề, tôi đã quyết định thực hiện quy tắc của Simpson. Nhưng tôi có thể kiểm tra xem trên thực tế, lỗi trong quy tắc của Simpson tổng hợp tỷ lệ với 1 / (N ^ 4) (không phải 1 / (N ^ 3) như bạn ngụ ý trong câu trả lời của bạn) không?
dùng2970116

1
Bạn có các công thức cho cũng như . Cái thứ nhất sử dụng các hệ số và thứ hai . 1/N31/N45/12,13/12,1,1...1,1,13/12,15/121/3,4/3,2/3,4/3...
Davidmh

9

Rất có thể là việc đánh giá các chức năng là phần tốn thời gian nhất của tính toán này. Nếu đó là trường hợp, thì bạn nên tập trung vào việc cải thiện tốc độ của func () thay vì cố gắng tăng tốc thói quen tích hợp.

Tùy thuộc vào các thuộc tính của func (), nhiều khả năng bạn có thể có được đánh giá chính xác hơn về tích phân với ít đánh giá hàm hơn bằng cách sử dụng công thức tích hợp phức tạp hơn.


1
Thật. Nếu chức năng của bạn trơn tru, thông thường bạn có thể thoát khỏi ít hơn 50 đánh giá chức năng nếu bạn đã sử dụng, giả sử, quy tắc bậc hai Gauss-4 chỉ trong 5 khoảng.
Wolfgang Bangerth

7

Khả thi? Đúng. Hữu ích? Không. Các tối ưu hóa tôi sẽ liệt kê ở đây dường như không thể tạo ra nhiều hơn một phần nhỏ của phần trăm chênh lệch trong thời gian chạy. Một trình biên dịch tốt có thể đã làm những điều này cho bạn.

Dù sao, nhìn vào vòng lặp bên trong của bạn:

    for (s=0,j=a;j<b;j+=h){
        func2 = func(j+h);
        s = s + 0.5*(func1+func2)*h;
        func1 = func2;
    }

Tại mỗi lần lặp lại, bạn thực hiện ba phép toán có thể được đưa ra bên ngoài: cộng j + h, nhân với 0.5và nhân với h. Cách đầu tiên bạn có thể khắc phục bằng cách bắt đầu biến lặp của mình tại a + hvà các biến khác bằng cách bao gồm các phép nhân:

    for (s=0, j=a+h; j<=b; j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

Mặc dù tôi sẽ chỉ ra rằng bằng cách thực hiện điều này, do lỗi vòng tròn dấu phẩy động, có thể bỏ lỡ lần lặp cuối cùng của vòng lặp. (Đây cũng là một vấn đề trong quá trình triển khai ban đầu của bạn.) Để giải quyết vấn đề đó, hãy sử dụng một unsigned inthoặc bộ size_tđếm:

    size_t n;
    for (s=0, n=0, j=a+h; n<N; n++, j+=h){
        func2 = func(j);
        s += func1+func2;
        func1 = func2;
    }
    s *= 0.5 * h;

Như câu trả lời của Brian nói, thời gian của bạn tốt hơn dành cho việc tối ưu hóa việc đánh giá chức năng func. Nếu độ chính xác của phương pháp này là đủ, tôi nghi ngờ bạn sẽ tìm thấy mọi thứ nhanh hơn cho cùng N. (Mặc dù bạn có thể chạy một số thử nghiệm để xem liệu Runge-Kutta có cho phép bạn hạ thấp Nđủ để việc tích hợp tổng thể mất ít thời gian hơn mà không làm mất độ chính xác hay không.)


4

Có một số thay đổi tôi muốn đề xuất để cải thiện tính toán:

  • Để có hiệu suất và độ chính xác, hãy sử dụng std::fma(), thực hiện phép cộng bội cộng .
  • Đối với hiệu suất, trì hoãn nhân diện tích của mỗi hình thang với 0,5 - bạn có thể làm điều đó một lần vào cuối.
  • Tránh bổ sung lặp đi lặp lại h, có thể tích lũy các lỗi làm tròn.

Ngoài ra, tôi sẽ thực hiện một số thay đổi cho rõ ràng:

  • Đặt cho hàm một tên mô tả nhiều hơn.
  • Hoán đổi thứ tự abtrong chữ ký hàm.
  • Đổi tên Nn, hdx, jx2, saccumulator.
  • Thay đổi nthành một int.
  • Khai báo các biến trong phạm vi chặt chẽ hơn.
#include <cmath>

double trapezoidal_integration(double func(double), double a, double b, int n) {
    double dx = (b - a) / (n - 1);   // Width of trapezoids

    double func_x1 = func(a);
    double accumulator = 0;

    for (int i = 1; i <= n; i++) {
        double x2 = a + i * dx;      // Avoid repeated floating-point addition
        double func_x2 = func(x2);
        accumulator = std::fma(func_x1 + func_x2, dx, accumulator); // Fused multiply-add
        func_x1 = func_x2;
    }

    return 0.5 * accumulator;
}

3

Nếu hàm của bạn là một đa thức, có thể có trọng số bởi một số hàm (ví dụ: gaussian), bạn có thể thực hiện tích hợp chính xác trong 3d với công thức hình khối (ví dụ: http://people.sc.fsu.edu/~jburkardt/c_src/ stroud / stroud.html ) hoặc với một lưới thưa thớt (ví dụ: http://tasmanian.ornl.gov/ ). Các phương thức này chỉ đơn giản chỉ định một tập hợp các điểm và trọng số để nhân giá trị hàm với, vì vậy chúng rất nhanh. Nếu chức năng của bạn đủ mượt để được xấp xỉ bằng đa thức, thì các phương pháp này vẫn có thể đưa ra một câu trả lời rất tốt. Các công thức dành riêng cho loại hàm bạn đang tích hợp, do đó, có thể cần một số công cụ đào để tìm đúng.


3

Khi bạn cố gắng tính toán một số nguyên, bạn cố gắng đạt được độ chính xác mà bạn muốn với nỗ lực nhỏ nhất có thể, hoặc cách khác, cố gắng để có được độ chính xác cao nhất có thể với một nỗ lực cố định. Bạn dường như hỏi làm thế nào để làm cho mã cho một thuật toán cụ thể chạy nhanh nhất có thể.

Điều đó có thể cung cấp cho bạn một số lợi ích nhỏ, nhưng nó sẽ ít. Có nhiều phương pháp hiệu quả hơn để tích hợp số. Google cho "quy tắc của Simpson", "Runge-Kutta" và "Fehlberg". Tất cả đều hoạt động khá giống nhau bằng cách đánh giá một số giá trị của hàm và khéo léo thêm bội số của các giá trị đó, tạo ra các lỗi nhỏ hơn nhiều với cùng số lượng đánh giá hàm hoặc cùng một lỗi với số lượng đánh giá nhỏ hơn nhiều.


3

Có rất nhiều cách để thực hiện tích hợp, trong đó quy tắc hình thang là đơn giản nhất.

Nếu bạn biết bất cứ điều gì về chức năng thực tế bạn đang tích hợp, bạn có thể làm tốt hơn nếu bạn khai thác điều đó. Ý tưởng là để giảm thiểu số lượng điểm lưới trong mức độ lỗi chấp nhận được.

Ví dụ, hình thang đang làm cho một tuyến tính phù hợp với các điểm liên tiếp. Bạn có thể thực hiện điều chỉnh bậc hai, nếu đường cong trơn tru sẽ phù hợp hơn, điều này có thể cho phép bạn sử dụng lưới thô hơn.

Mô phỏng quỹ đạo đôi khi được thực hiện bằng cách sử dụng hình nón, bởi vì quỹ đạo rất giống với các phần hình nón.

Trong công việc của tôi, chúng tôi đang tích hợp các hình dạng gần đúng với các đường cong hình chuông, vì vậy sẽ hiệu quả khi mô hình hóa chúng như thế ( phương pháp Gaussian thích nghi được coi là "tiêu chuẩn vàng" trong công việc này).


1

Vì vậy, như đã được chỉ ra trong các câu trả lời khác, điều này phụ thuộc rất nhiều vào mức độ đắt đỏ của chức năng của bạn. Tối ưu hóa mã bẫy của bạn chỉ có giá trị nếu nó thực sự là nút cổ chai của bạn. Nếu nó không hoàn toàn rõ ràng, bạn nên kiểm tra điều này bằng cách định hình mã của bạn (các công cụ như Intels V-Tune, Valgrind hoặc Visual Studio có thể làm điều này).

Tuy nhiên tôi sẽ đề xuất một cách tiếp cận hoàn toàn khác: tích hợp Monte Carlo . Ở đây bạn chỉ cần xấp xỉ tích phân bằng cách lấy mẫu hàm của bạn tại các điểm ngẫu nhiên thêm kết quả. Xem pdf này ngoài trang wiki để biết chi tiết.

Điều này hoạt động rất tốt đối với dữ liệu chiều cao, thường tốt hơn nhiều so với các phương pháp bậc hai được sử dụng trong tích hợp 1-d.

Trường hợp đơn giản rất dễ thực hiện (xem pdf), chỉ cần cẩn thận rằng hàm ngẫu nhiên tiêu chuẩn trong c ++ 98 khá tệ cả về hiệu suất và chất lượng khôn ngoan. Trong c ++ 11, bạn có thể sử dụng Mersenne Twister.

Nếu chức năng của bạn có nhiều biến thể trong một số lĩnh vực và ít hơn ở những khu vực khác, hãy xem xét sử dụng lấy mẫu phân tầng. Tôi khuyên bạn nên sử dụng thư viện khoa học GNU , thay vì viết riêng của bạn.


1
Tôi thực sự đang thực hiện tích hợp 3D, trong đó mã này được gọi là đệ quy.

"đệ quy" là chìa khóa. Bạn đang trải qua một tập dữ liệu lớn và xem xét nhiều dữ liệu hơn một lần hoặc bạn thực sự đang tự tạo tập dữ liệu của mình từ các hàm (piecewise?).

Tích hợp được đánh giá đệ quy sẽ rất tốn kém, và vô lý một cách lố bịch khi sức mạnh tăng lên trong đệ quy.

Tạo một mô hình để nội suy tập dữ liệu của bạn và thực hiện tích hợp biểu tượng từng phần. Do rất nhiều dữ liệu sau đó được thu gọn thành các hệ số của các hàm cơ sở, nên độ phức tạp cho đệ quy sâu hơn sẽ tăng lên một cách đa thức (và thường là các công suất khá thấp) thay vì theo cấp số nhân. Và bạn nhận được kết quả "chính xác" (bạn vẫn cần tìm ra các sơ đồ đánh giá tốt để có được hiệu suất số hợp lý, nhưng vẫn nên khả thi để có được kết quả tốt hơn so với tích hợp hình thang).

Nếu bạn xem các ước tính lỗi cho các quy tắc hình thang, bạn sẽ thấy rằng chúng có liên quan đến một số đạo hàm của các hàm liên quan và nếu tích hợp / định nghĩa được thực hiện theo cách đệ quy, các hàm sẽ không có xu hướng hoạt động tốt .

Nếu công cụ duy nhất của bạn là một cái búa, mọi vấn đề trông giống như một cái đinh. Trong khi bạn hầu như không chạm vào vấn đề trong mô tả của mình, tôi có nghi ngờ rằng việc áp dụng quy tắc hình thang một cách đệ quy là một kết quả xấu: bạn nhận được một sự bùng nổ của cả hai yêu cầu không chính xác và tính toán.


1

1/21/2

    double trap(double func(double), double b, double a, double N){
double j, s;
double h = (b-a)/(N-1.0); //Width of trapezia

double s = 0;
j = a;
for(i=1; i<N-1; i++){
  j += h;
  s += func(j);
}
s += (func(a)+func(b))/2;

return s*h;
}

1
Vui lòng đưa ra lý do cho những thay đổi và mã của bạn. Một khối mã khá vô dụng đối với hầu hết mọi người.
Godric Seer

Đã đồng ý; hãy giải thích câu trả lời của bạn
Geoff Oxberry
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.