이 통합 코드를 최적화하여 더 빠르게 실행할 수 있습니까?


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;
}

위의 내용은 trapezia를 사용하여 func()한도 사이 의 1D 수치 적분 (확장 된 사다리꼴 규칙 사용)에 대한 C ++ 코드입니다 .[a,b]N1

실제로이 코드를 재귀 적으로 호출하는 3D 통합을 수행하고 있습니다. 나는 작업하여 적절한 결과를 얻습니다.N=50

더 줄이는 것 외에는 더 빨리 실행되도록 위의 코드를 최적화하는 방법을 제안 할 수 있습니까? 아니면 더 빠른 통합 방법을 제안 할 수 있습니까?N


5
이것은 실제로 질문과 관련이 없지만 더 나은 변수 이름을 선택하는 것이 좋습니다. 같이 trapezoidal_integration하는 대신 trap, sumrunning_total대신 s(또한 사용하는 +=대신 s = s +), trapezoid_width또는 dx대신 h(또는하지, 원하는 사다리꼴 규칙에 대한 표기법에 따라), 변화 func1func2그들이 값이 아닌 기능을한다는 사실을 반영 할 수 있습니다. 예를 들어 func1-> previous_valuefunc2-> current_value또는 이와 유사한 것입니다.
David Z

답변:


5

수학적으로 표현은 다음과 같습니다.

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

그래서 당신은 그것을 구현할 수 있습니다. 앞서 말했듯이 시간은 아마도 함수 평가에 의해 좌우 될 수 있으므로 동일한 정확도를 얻으려면 더 적은 함수 평가가 필요한 더 나은 통합 방법을 사용할 수 있습니다.

가우시안 구적법은 현대에는 장난감 이상입니다. 평가가 거의 필요 하지 않은 경우에만 유용합니다 . 구현하기 쉬운 것을 원한다면 Simpson의 규칙을 사용할 수는 있지만 주문보다 더 나아갈 수는 없습니다.1/N3 정당한 이유없이

함수의 곡률이 많이 변하는 경우, 함수가 평평 할 때 더 큰 단계를 선택하고 곡률이 높을 때 더 작은 단계를 선택하는 적응 단계 루틴을 사용할 수 있습니다.


나가서 문제로 돌아온 후, 나는 심슨의 규칙을 이행하기로 결정했습니다. 그러나 실제로 복합 심슨 규칙의 오류가 1 / (N ^ 4)에 비례한다는 것을 확인할 수 있습니까?
user2970116

1
에 대한 수식이 있습니다 1/N3 만큼 잘 1/N4. 첫 번째는 계수를 사용합니다5/12,13/12,1,1...1,1,13/12,15/12 그리고 두 번째 1/3,4/3,2/3,4/3....
Davidmh

9

함수 계산이이 계산에서 가장 많은 시간이 소요될 수 있습니다. 이 경우 통합 루틴 자체의 속도를 높이기보다는 func ()의 속도를 향상시키는 데 중점을 두어야합니다.

func ()의 속성에 따라 더 복잡한 적분 공식을 사용하여 함수 평가가 적은 적분을보다 정확하게 평가할 수 있습니다.


1
과연. 함수가 매끄러 우면 가우스 -4 구적법 규칙을 5 회 간격으로 사용한 경우 일반적으로 50 개 미만의 함수 평가로 벗어날 수 있습니다.
Wolfgang Bangerth 2014 년

7

가능한? 예. 유능한? 아니요. 여기에 나열 할 최적화는 런타임에서 약간의 퍼센트 차이를 만들지 않을 것입니다. 좋은 컴파일러는 이미 당신을 위해 이것을 할 수 있습니다.

어쨌든 내부 루프를 살펴보십시오.

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

모든 루프 반복 j + h에서 덧셈, 곱셈 0.5및 곱셈의 세 가지 수학 연산을 수행 할 수 있습니다 h. 첫 번째는에서 반복자 변수를 시작하여 고칠 수 a + h있으며 다른 것은 곱셈을 고려 하여 해결할 수 있습니다 .

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

이 작업을 수행하면 부동 소수점 반올림 오류로 인해 루프의 마지막 반복을 놓칠 수 있습니다. (이것은 원래 구현에서도 문제였습니다.) 그 문제를 해결하려면 unsigned int또는 size_t카운터를 사용하십시오.

    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;

Brian의 답변에서 알 수 있듯이 함수 평가를 최적화하는 데 시간이 더 많이 걸립니다 func. 이 방법의 정확성이 충분하다면, 같은 방법으로 더 빠른 것을 찾을 수있을 것 N입니다. (Runge-Kutta를 사용하면 N정확도를 떨어 뜨리지 않으면 서 전체 통합에 시간이 덜 걸리 도록 충분히 낮출 수 있는지 테스트하기 위해 일부 테스트를 실행할 수는 있습니다 .)


4

계산을 개선하기 위해 권장하는 몇 가지 변경 사항이 있습니다.

  • 성능과 정확성을 위해 fused multiply-addstd::fma() 를 수행하는을 사용 하십시오 .
  • 성능을 위해 각 사다리꼴의 넓이에 0.5를 곱하면됩니다. 마지막에 한 번만 할 수 있습니다.
  • h반올림 오차가 누적 될 수있는을 반복해서 추가하지 마십시오 .

또한 명확성을 위해 몇 가지 사항을 변경했습니다.

  • 함수에보다 구체적인 이름을 지정하십시오.
  • 의 순서로 교체 ab함수 서명에 있습니다.
  • Nn, hdx, jx2, s→로 이름을 변경하십시오 accumulator.
  • 변경 nint.
  • 더 좁은 범위에서 변수를 선언하십시오.
#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

함수가 다항식이고 일부 함수 (예 : 가우시안)에 의해 가중 될 경우 Cubture 수식 (예 : http://people.sc.fsu.edu/~jburkardt/c_src/ stroud / stroud.html ) 또는 희소 그리드 (예 : http://tasmanian.ornl.gov/ )를 사용하십시오. 이 방법은 단순히 함수 값을 곱하기 위해 점과 가중치 세트를 지정하므로 매우 빠릅니다. 함수가 다항식으로 근사 할 정도로 매끄러 우면 이러한 방법으로 여전히 좋은 대답을 얻을 수 있습니다. 수식은 통합하려는 함수 유형에 따라 다르므로 올바른 수식을 찾으려면 약간의 파고가 필요할 수 있습니다.


3

적분을 수치로 계산하려고 할 때 가능한 적은 노력으로 원하는 정밀도를 얻으려고하거나 고정 노력으로 가능한 최고의 정밀도를 얻으려고 시도하십시오. 특정 알고리즘에 대한 코드를 최대한 빨리 실행하는 방법을 묻는 것 같습니다.

그것은 약간의 이득을 줄 수 있지만, 그것은 조금 될 것입니다. 수치 적분에는 훨씬 더 효율적인 방법이 있습니다. "Simpson 's rule", "Runge-Kutta"및 "Fehlberg"에 대한 Google 그것들은 함수의 일부 값을 평가하고 그 값의 배수를 영리하게 추가하여 동일한 수의 함수 평가로 훨씬 작은 오류를 생성하거나 훨씬 적은 수의 평가로 동일한 오류를 생성함으로써 모두 매우 유사하게 작동합니다.


3

사다리꼴 규칙이 가장 간단한 통합 방법에는 여러 가지가 있습니다.

통합하고있는 실제 기능에 대해 아는 것이 있으면이를 활용하면 더 잘 수행 할 수 있습니다. 허용 가능한 수준의 오류 내에서 그리드 포인트 수를 최소화하는 것이 좋습니다.

예를 들어, 사다리꼴은 연속 점에 선형으로 적합합니다. 이차 적합을 만들 수 있습니다. 곡선이 매끄러 우면 더 적합하게되어 더 거친 격자를 사용할 수 있습니다.

궤도는 원뿔 섹션과 매우 유사하기 때문에 궤도 시뮬레이션은 때때로 원뿔을 사용하여 수행됩니다.

필자의 작업에서는 종 모양의 곡선에 가까운 모양을 통합하므로이를 모델링하는 것이 효과적입니다 ( 적응 가우스 구적법 은이 연구에서 "골드 표준"으로 간주 됨).


1

따라서 다른 답변에서 지적했듯이 이것은 함수의 비용이 얼마나 많은지에 달려 있습니다. trapz 코드를 최적화하는 것은 실제로 병목 현상 인 경우에만 가치가 있습니다. 명확하지 않은 경우 코드를 프로파일 링하여이를 확인해야합니다 (Intels V-tune, Valgrind 또는 Visual Studio와 같은 도구가이를 수행 할 수 있음).

그러나 나는 완전히 다른 접근법을 제안 할 것이다 : Monte Carlo integration . 여기서는 결과를 추가하는 임의의 지점에서 함수를 샘플링하여 적분을 근사화합니다. 참조 자세한 내용은 위키 페이지에 추가하여 PDF를.

이것은 고차원 데이터에 매우 효과적이며, 일반적으로 1 차원 통합에 사용되는 직교 법보다 훨씬 낫습니다.

간단한 경우는 구현하기가 매우 쉽습니다 (pdf 참조). c ++ 11에서는에서 Mersenne Twister를 사용할 수 있습니다.

함수에 따라 일부 영역에서는 차이가 많고 다른 영역에서는 차이가 적은 경우 계층화 된 샘플링 사용을 고려하십시오. 그래도 직접 작성하는 대신 GNU Scientific 라이브러리를 사용하는 것이 좋습니다 .


1
실제로이 코드를 재귀 적으로 호출하는 3D 통합을 수행하고 있습니다.

"재귀 적으로"가 핵심입니다. 큰 데이터 세트를 거치고 많은 데이터를 두 번 이상 고려하거나 실제로 (조각?) 함수에서 직접 데이터 세트를 생성하고 있습니다.

재귀 적으로 평가 된 통합은 엄청나게 비싸고 재귀의 힘이 증가함에 따라 엄청나게 부정확합니다.

데이터 세트를 보간하기위한 모델을 작성하고 조각 별 기호 통합을 수행하십시오. 많은 데이터가 기본 함수의 계수로 붕괴되기 때문에 더 깊은 재귀에 대한 복잡성은 기하 급수적으로 다항식 적으로 (대개 다소 낮은 전력) 증가합니다. 그리고 "정확한"결과를 얻을 수 있습니다 (합리적인 수치 성능을 얻기 위해서는 여전히 좋은 평가 체계를 찾아야하지만 사다리꼴 통합보다 더 나아지는 것이 여전히 가능해야합니다).

사다리꼴 규칙에 대한 오류 추정치를 살펴보면 규칙이 관련 함수의 일부 파생과 관련이 있으며 통합 / 정의가 재귀 적으로 수행되는 경우 함수가 올바르게 파생 된 파생물을 갖지 않는 경향이 있습니다. .

유일한 도구가 망치라면 모든 문제는 못처럼 보입니다. 설명에서 문제를 거의 다루지 않지만, 사다리꼴 규칙을 재귀 적으로 적용 하는 것은 좋지 않은 것으로 의심 됩니다. 부정확성과 계산 요구 사항이 모두 폭발적으로 나타납니다.


1

원래 코드는 각 N 포인트에서 함수를 평가 한 다음 값을 더한 다음 합계에 단계 크기를 곱합니다. 유일한 트릭은 시작과 끝에 값이 가중치로 추가된다는 것입니다1/2내부의 모든 포인트는 전체 무게로 추가됩니다. 실제로, 그들은 또한 무게와 함께 추가1/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
변경 및 코드에 대한 추론을 제공하십시오. 코드 블록은 대부분의 사람들에게 상당히 쓸모가 없습니다.
Godric Seer

합의 답을 설명하십시오.
Geoff Oxberry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.