#+title: Documentation for AutoSW #+title: Topic 5: Numerical integration methods #+author: Mia Kronner #+author: Matr. 22201686 #+options: email:nil #+bibliography: ./bib.bib #+LATEX_HEADER: \let\oldsection\section #+LATEX_HEADER: \renewcommand{\section}{\clearpage\oldsection} #+LATEX_HEADER: \let\oldsubsection\subsection #+LATEX_HEADER: \renewcommand{\subsection}{\clearpage\oldsubsection} * Declaration of self reliance I hereby declare that I have written this thesis independently in accordance with ยง 35 para. 7 RaPO (Framework Examination Regulations for Universities of Applied Sciences in Bavaria, BayRS 2210-4-1-4-1-WFK), have not submitted it elsewhere for examination purposes, have not used any sources or aids other than those indicated, and have marked all direct and indirect quotations as such. * Graphical representation of the software design #+BIND: org-latex-images-centered nil #+ATTR_LATEX: \textwidth :float nil [[./graphical-representation-white.drawio.png]] * Theoretical explanation of the relationships ** ~"integrate.h"~: Interface for ~integrate.c~ Module. Here is declared: - a void float function pointer for the mathematical function which is to be integrated ~integrand_f~. - a typedef which contains the possible return values for the three integration functions ~integ_status_t~ - the three integration functions ~midpoint~ (rectangle midpoint method), ~trapezoid~ (trapezoid method), and ~simpson~ (simpson 1/3 method) + they each take a integrand function ~integrand_f f~, its context as a void pointer ~void *ctx~, the limits ~a~ and ~b~ and the number of intevals ~n~ #+name: integrate.h #+begin_src C :noweb-ref integrate.h #ifndef INTEGRATE_H_ #define INTEGRATE_H_ #include typedef float (*integrand_f)(float x, void *ctx); typedef enum { INTEG_OK = 0, INTEG_ERR_BAD_ARGS = -1, INTEG_ERR_N_EVEN_REQUIRED = -2 } integ_status_t; integ_status_t midpoint(integrand_f f, void *ctx, float a, float b, unsigned n, float *out); integ_status_t trapezoid(integrand_f f, void *ctx, float a, float b, unsigned n, float *out); integ_status_t simpson(integrand_f f, void *ctx, float a, float b, unsigned n, float *out); #endif // INTEGRATE_H_ #+end_src ** ~"integrate.c"~: Implements the three numerical integration algorithms (midpoint/rectangle, trapezoid, Simpson 1/3) behind the public interface from ~integrate.h~, including argument validation and method-specific constraints. The file includes ~integrate.h~ and provides the concrete implementations of ~midpoint()~, ~trapezoid()~, and ~simpson()~, each computing an approximation of $$I = \int_a^bf(x)dx$$ by sampling the integrand at specific points and summing weighted contributions. All three functions follow the same reusable calling convention: function pointer ~integrand_f f~, a generic context pointer ~ctx~, integration bounds ~a~, ~b~, subinterval count ~n~, and output pointer ~out~. #+name: integrate.c #+begin_src C :noweb-ref integrate.c static inline int bad_args(integrand_f f, float *a, float *b, unsigned *n, float *out) { return (f == NULL || out == NULL || *n == 0u || !(*b > *a)); } integ_status_t midpoint(integrand_f f, void *ctx, float a, float b, unsigned n, float *out) { /*check if all necessary parameters have been given */ if (bad_args(f, &a, &b, &n, out)) return INTEG_ERR_BAD_ARGS; float sum = 0.0f; float fx = 0.0f; /*intervall width h: */ const float h = (b - a) / (float)n; float x = a + 0.5f * h; for (unsigned i = 0; i < n; ++i) { fx = f(x, ctx); sum = sum + fx; x = x + h; } *out = sum * h; return INTEG_OK; } integ_status_t trapezoid(integrand_f f, void *ctx, float a, float b, unsigned n, float *out) { if (bad_args(f, &a, &b, &n, out)) { return INTEG_ERR_BAD_ARGS; } const float h = (b - a) / (float)n; float sum = 0.5f * (f(a, ctx) + f(b, ctx)); float x = a + h; for (unsigned i = 1; i < n; ++i) { sum = sum + f(x, ctx); x = x + h; } *out = sum * h; return INTEG_OK; } integ_status_t simpson(integrand_f f, void *ctx, float a, float b, unsigned n, float *out) { if (bad_args(f, &a, &b, &n, out)) { return INTEG_ERR_BAD_ARGS; } if (n & 1u) /*n must be even*/ { return INTEG_ERR_N_EVEN_REQUIRED; } float sum_odd = 0.0f; float sum_even = 0.0f; const float h = (b - a) / (float)n; float x = a + h; for (unsigned i = 1; i < n; ++i) { const float fx = f(x, ctx); if (i & 1u) sum_odd = sum_odd + fx; else sum_even = sum_even + fx; x = x + h; } *out = (h / 3.0f) * (f(a, ctx) + 4.0f * sum_odd + 2.0f * sum_even + f(b, ctx)); return INTEG_OK; } #+end_src *** Common argument checking (~bad_args~) A shared internal helper ~bad_args()~ is implemented as ~static inline~ to reduce call overhead and to allow the compiler to inline it for performance on embedded targets. It returns an error condition if ~f == NULL, out == NULL, n == 0~ or the interval is invalid (the code checks ~b > a~). If ~bad_args()~ reports invalid inputs, each public integration function returns ~INTEG_ERR_BAD_ARGS~ immediately. *** Midpoint / rectangle method (~midpoint~) **** Mathematical idea The interval [a,b] is split into n subintervals of equal width $$h=(b-a)/n$$, and each subinterval [xi,xi+1] is approximated by a rectangle whose height is the function value at the midpoint $$xi+h/2$$. The Formula is as follows: [cite:@Midpoint] $$\approx h \sum_{i=1}^n f({x_{i-1}+x_i}/2)$$ **** Implementation details The code computes ~h~, initializes the first midpoint ~x = a + 0.5fh~, then loops exactly ~n~ times, accumulating ~sum = sum + f(x, ctx)~ and stepping ~x = x + h~. Finally, the result is written as ~out = sum * h~ and the function returns ~INTEG_OK~. *** Trapezoid method (~trapezoid~) **** Mathematical idea Each subinterval is approximated by a trapezoid formed by the points $$(xi,f(xi))$$ and $$(xi+1,f(xi+1))$$, giving the composite trapezoid rule. This corresponds to the standard weighted sum [cite:@Trapez] $$ = h/2 (f(x_0)+2f(x_1)+\dots+2f(x_{n-1})+f(x_n))$$ **** Implementation details The code computes ~h~, then initializes ~sum = 0.5f*(f(a,ctx)+f(b,ctx))~ to apply the half-weight to the endpoints. It iterates from the first interior node ~x = a + h~ for ~i = 1..n-1~, adds each interior sample once (~sum = sum + f(x,ctx)~), then scales by ~h~ via ~*out = sum * h~. *** Simpson 1/3 method (~simpson~) **** Mathematical idea Simpson's 1/3 rule approximates the function by piecewise quadratic polynomials over pairs of subintervals, which leads to alternating weights 4 and 2 for interior points. The composite Simpson rule requires an even number of subintervals nn so that the domain can be grouped into $$n/2$$ pairs. The formula is as such: [cite:@Simpson] $$ \approx (h/3) [f(x_0)+4f(x_1)+2f(x_2)+ \dots 2f(x_{n-2})+4f(x_{n-1})+f(x_n)]$$ **** Implementation details and constraint handling After basic argument validation, the function checks ~if (n & 1u)~ and returns ~INTEG_ERR_N_EVEN_REQUIRED~ if ~n~ is odd, enforcing the "even n" requirement from the interface contract. It then loops over the interior nodes ~i=1..n-1~ and accumulates two partial sums: ~sum_odd~ for odd indices (weight 4) and ~sum_even~ for even indices (weight 2). The final formula implemented is \\ ~out = (h/3)(f(a)+4*sum_odd+2*sum_even+f(b))~, which matches the documented Simpson weighting scheme. ** Performance-oriented aspects (embedded focus) The design avoids dynamic allocation and uses a caller-provided output pointer (~float *out~), which is predictable and typical for embedded C modules. The ~ctx~ pointer allows passing coefficients/parameters without global variables, enabling reuse for many function types while keeping the integrator code generic. Using ~static inline~ for ~bad_args~ and keeping loop bodies simple (incrementing ~x~ by ~h~ rather than recomputing from scratch) supports compiler optimization and reduces runtime overhead. When checking for evenness of ~n~ the simpson function uses ~& u1~ instead of ~% 2~. It is not possible to substitue a ~/2~ for a ~>>1~ for the float variable type. Such tricks can not have been used for any values with the float type. * Documentation of the reference examples used for testing ~"main.c"~ is a small command-line test application that demonstrates how to use the reusable integration module by defining example integrand functions, calling all three numerical methods, and printing absolute errors against known exact results. The application includes ~integrate.h~ and uses standard library functionality (printing and absolute error via ~fabsf~) to compare the numeric results to a known reference ("exact") value. Its purpose is not to be a generic framework, but a compact test harness that exercises the module API and makes method limitations visible at runtime (e.g., Simpson's even-~n~ requirement). ** Test integrand design Parameterized quadratic via context pointer A small struct ~quad_t~ stores coefficients ~a2~, ~a1~, ~a0~ for a quadratic polynomial $$f(x)=a_2x^2+a_1x+a_0$$, showing how the integrator's ~void *ctx~ can carry user-defined parameters without globals. The function ~f_quad(float x, void ctx)~ casts ~ctx~ to ~const quad_t~ and evaluates the polynomial, matching the generic ~integrand_f~ interface expected by the integration module. ** Test runner (~run_one~) The helper ~run_one(...)~ calls ~midpoint~, ~trapezoid~, and ~simpson~ with the same function, bounds, and number of subintervals, storing results into three local floats (~r~, ~t~, ~s~). It prints the integration problem setup (function name, bounds, ~n~), prints the exact reference value, and prints each method's absolute error using ~fabsf(result - exact)~ for a direct precision comparison. Simpson's status return is checked: if ~simpson~ returns ~INTEG_OK~, the Simpson result/error is printed, otherwise a message is printed indicating it was not computed because ~n~ must be even. ** Reference examples in ~main()~ Four concrete test cases are instantiated using ~quad_t~ coefficients and passed to ~run_one~. They have been chosen in such a way that many edge cases are accounted for. Example 1: ~q~ $$f(x)=x^2$$ on [0,1] with exact integral 1/3 and ~n=10~ [[./q.png]] Example 2: ~q2~ $$f(x)=-x2+3x+50$$ on [-3,15] with exact integral 90 and ~n=22~ [[./q2.png]] Example 3: ~q3~ $$f(x)=-9x^2+3x-100$$ on [12,15] with exact integral -5119.5 and ~n=2~ [[./q3.png]] Example 4: ~q4~ $$f(x)=6x^2-10x$$ on [-50,-5] with exact integral 262125 and ~n=6u~ [[./q4.png]] #+name: main.c #+begin_src C :noweb yes :results output :flags -g -fanalyzer <> <> #include #include typedef struct { float a2, a1, a0; } quad_t; static float f_quad(float x, void *ctx) { const quad_t *q = (const quad_t *)ctx; return (q->a2 * x * x) + (q->a1 * x) + q->a0; } static void run_one(const char *name, integrand_f f, void *ctx, float a, float b, float exact, unsigned n) { float r = 0.0f, t = 0.0f, s = 0.0f; integ_status_t stS; midpoint(f, ctx, a, b, n, &r); trapezoid(f, ctx, a, b, n, &t); stS = simpson(f, ctx, a, b, n, &s); printf("\n%s on [%.6f, %.6f], n=%u\n", name, a, b, n); printf("Exact: %.9f\n", exact); printf("Midpoint: %.9f err=%.9f\n", r, fabsf(r - exact)); printf("Trapezoid: %.9f err=%.9f\n", t, fabsf(t - exact)); if (stS == INTEG_OK) printf("Simpson: %.9f err=%.9f\n", s, fabsf(s - exact)); else printf("Simpson: not computed (n must be even)\n"); } int main(void) { const quad_t q = {1.0f, 0.0f, 0.0f}; /*x^2*/ const quad_t q2 = {-1.0f, 3.0f, 50.0f}; /*x^2+3x+50*/ const quad_t q3 = {-9.0f, 3.0f, -100.0f}; /*-9x^2+3x-100*/ const quad_t q4 = {6.0f, -10.0f, 0.0f}; /*6x^2-10x*/ run_one("x^2", f_quad, (void *)&q, 0.0f, 1.0f, 1.0f / 3.0f, 10u); run_one("x^2+3x+50", f_quad, (void *)&q2, -3.0f, 15.0f, 90.0f, 22u); run_one("-9x^2+3x-100", f_quad, (void *)&q3, 12.0f, 15.0f, -5119.5f, 2u); run_one("6x^2-10x", f_quad, (void *)&q4, -50.0f, -5.0f, 262125.0f, 6u); return 0; } #+end_src *** Results #+RESULTS: main.c #+begin_results x^2 on [0.000000, 1.000000], n=10 Exact: 0.333333343 Midpoint: 0.332500070 err=0.000833273 Trapezoid: 0.335000038 err=0.001666695 Simpson: 0.333333373 err=0.000000030 x^2+3x+50 on [-3.000000, 15.000000], n=22 Exact: 90.000000000 Midpoint: 91.004028320 err=1.004028320 Trapezoid: 87.991584778 err=2.008415222 Simpson: 89.999839783 err=0.000160217 -9x^2+3x-100 on [12.000000, 15.000000], n=2 Exact: -5119.500000000 Midpoint: -5114.437500000 err=5.062500000 Trapezoid: -5129.625000000 err=10.125000000 Simpson: -5119.500000000 err=0.000000000 6x^2-10x on [-50.000000, -5.000000], n=6 Exact: 262125.000000000 Midpoint: 260859.375000000 err=1265.625000000 Trapezoid: 264656.250000000 err=2531.250000000 Simpson: 262125.000000000 err=0.000000000 #+end_results * Bibliography #+CITE_EXPORT: csl ./ieee.csl #+print_bibliography: