diff --git a/src/simulation/Integrators.ts b/src/simulation/Integrators.ts index 33324467dd2eec4b1cd375ed47623cb68e7dd748..571b7a4579c674d8e98742ba01f5b96e04493d9d 100644 --- a/src/simulation/Integrators.ts +++ b/src/simulation/Integrators.ts @@ -1,3 +1,13 @@ +import * as math from "mathjs"; + +export function calculate_error(a: Array<number>, b: Array<number>): Array<number> { + let error: Array<number> = []; + for (let i = 0; i < a.length; i++) { + error.push(Math.abs(a[i] - b[i])); + } + return error; + } + export function Euler(x: Array<number>, h: number, f: (x: Array<number>) => Array<number>): Array<number> { let y = f(x); let x_new = x.map((_, i) => x[i] + h * y[i]); @@ -20,4 +30,13 @@ export function Runge_Kutta(x: Array<number>, h: number, f: (x: Array<number>) = return x_new; } - +export function Adaptive_step_size(integrator: (x: Array<number>, h: number, f: (x: Array<number>) => Array<number>) => Array<number>, + x: Array<number>, h: number, f: (x: Array<number>) => Array<number>, error_max: number, error_list?: Array<number>): number { + let x_a = integrator(x, h, f); + let x_b = integrator(x, h / 2, f); + let error = calculate_error(x_a, x_b); + let approximated_error = error.reduce((a, b) => Math.max(a, b)); + + if (error_list) error_list.push(approximated_error); + return h * math.sqrt(error_max / approximated_error.valueOf()); +} diff --git a/tests/Integrators.test.ts b/tests/Integrators.test.ts index 94050ccde9d37c70a73eb2b8cbdefaf29a8fe7d6..cf075b6114b76876c4a14f7e80de917afa387db9 100644 --- a/tests/Integrators.test.ts +++ b/tests/Integrators.test.ts @@ -1,41 +1,23 @@ import * as Integrator from "../src/simulation/Integrators"; import * as math from "mathjs"; import * as fs from "fs"; -// select a differential equation with a known solution -// format numbers to a fixed number of decimal places -function format_number(number: number, decimal_places: number): string { - return number.toFixed(decimal_places); -} - -// find minimum for decimal places -function decimal_places(n: number): number { - let decimal_places = 0; - while (n < 1) { - n = n * 10; - decimal_places++; - } - return decimal_places+1; -} +function write_table(result: Array<number>, exact: Array<number>, error: Array<number>, h_list: Array<number>, + title: string, decimal_places: number, error_list?: Array<number>): void { + let format_number = (number: number, decimal_places: number): string => Number(number).toFixed(decimal_places); -function min_elem(arrs: Array<Array<number>>): number { - let min = arrs[0][0]; - for (let i = 0; i < arrs.length; i++) { - for (let j = 0; j < arrs[i].length; j++) { - if (arrs[i][j] < min) { - min = arrs[i][j]; - } - } - } - return min; -} + let table = [["h", "exact", "result", "error", "error_estimated"]]; -function write_table(result: Array<number>, exact: Array<number>, error: Array<number>, title: string, bins: number, decimal_places: number): void { - let table = [["x", "exact", "result", "error"]]; for (let i = 0; i < result.length; i++) { - table.push([format_number(i / bins, decimal_places), format_number(exact[i], decimal_places), format_number(result[i], decimal_places), format_number(error[i], decimal_places)]); + table.push([ + format_number(h_list[i], decimal_places), + format_number(exact[i], decimal_places), + format_number(result[i], decimal_places), + format_number(error[i], decimal_places), + (error_list ? format_number(error_list[i], decimal_places) : "n/a") + ]); } - + let table_string = ""; for (let i = 0; i < table.length; i++) { for (let j = 0; j < table[i].length; j++) { @@ -45,7 +27,7 @@ function write_table(result: Array<number>, exact: Array<number>, error: Array<n table_string += "\n"; } - fs.writeFileSync(`${title}.txt`, table_string); + fs.writeFileSync(`data_${title}.txt`, table_string); } function calculate_error(result: Array<number>, exact: Array<number>): Array<number> { @@ -56,60 +38,131 @@ function calculate_error(result: Array<number>, exact: Array<number>): Array<num return error; } -describe("Integrators", () => { - - let k = 0.5 - let c = 2 - let g = (x: number): number => c * math.exp(-k * x); - let init_value = g(0); - - // find the next y value for function g(x) - let f = (x: Array<number>): Array<number> => [-k * x[0]]; - - // compare values betwenn [0, 1] - let bins = 100; - let h = 1 / bins; - - // calculate the exact solution - let exact: Array<number> = []; - for(let i = 0; i <= bins; i++) { - let x = i / bins; - let y = g(x); - exact.push(y); - } +interface Result { + exact: Array<number>; + estimate: Array<number>; + error: Array<number>; +} + +// exact solution +function g(x: number): number { + return 2 * math.exp(-0.5 * x); +}; + +// differential equation +function f(x: Array<number>): Array<number> { + return [-0.5 * x[0]]; +} + +// INTERVAL CHECK +const x_max = 2 +const error_max = 0.01; +const error_acc = 0.8; // 80% of error_estimates are less then max_error + +// FIXED STEP SIZE +function fixed_step_size(integrator: (x: Array<number>, h: number, f: (x: Array<number>) => Array<number>) => Array<number>, + h: number, f: (x: Array<number>) => Array<number>, name: string): void { + let h_list: Array<number> = []; + + let result: Result = { + exact: [], + estimate: [], + error: [] + }; + + h_list.push(0); + let h_accum = h; + result.estimate.push(g(h_list[0])); + while(h_accum < x_max) { + let tmp = integrator([result.estimate[result.estimate.length - 1]], h, f); + result.estimate.push(tmp[0]); + h_list.push(h_accum); + h_accum += h; + } + + for (let i = 0; i < h_list.length; i++) { + result.exact.push(g(h_list[i])); + } + + result.error = calculate_error(result.estimate, result.exact); + + write_table(result.estimate, result.exact, result.error, h_list, name, 10); - // evaluation loop - function eval_func(integrator: (x: Array<number>, h: number, f: (x: Array<number>) => Array<number>) => Array<number>, init: number): Array<number> { - let result: Array<number> = [init]; - let x = [init] - for(let i = 0; i < bins; i++) { - x = integrator(x, h, f); - result.push(x[0]); + result.error.forEach(e => { + expect(e).toBeLessThan(1); + }); + +} + +function adaptive_step_size(integrator: (x: Array<number>, h: number, f: (x: Array<number>) => Array<number>) => Array<number>, + h: number, f: (x: Array<number>) => Array<number>, name: string): void { + let h_list: Array<number> = []; + let error_list: Array<number> = []; + + let result: Result = { + exact: [], + estimate: [], + error: [] + }; + + h_list.push(0); + let h_accum = h; + result.estimate.push(g(h_list[0])); + while(h_accum < x_max) { + h = Integrator.Adaptive_step_size(integrator, [result.estimate[result.estimate.length - 1]], h, f, error_max, error_list); + result.estimate.push(integrator([result.estimate[result.estimate.length - 1]], h, f)[0]); + h_accum += h; + h_list.push(h_accum); } - return result; + + for (let i = 0; i < h_list.length; i++) { + result.exact.push(g(h_list[i])); + } + + result.error = calculate_error(result.estimate, result.exact); + + write_table(result.estimate, result.exact, result.error, h_list, name, 10, error_list); + + let error_correct_estimate = 0; + error_list.forEach(e => { + if (e < error_max) error_correct_estimate++; + }); + + expect(error_correct_estimate / error_list.length).toBeGreaterThan(error_acc); + } - let max_error = 0.1; - // calculate the Euler solution - it("Euler", () => { - let result = eval_func(Integrator.Euler, init_value); - let error = calculate_error(result, exact); - error.forEach((e, i) => { expect(e).toBeLessThan(max_error); }); - write_table(result, exact, error, "Euler", bins, 5); + +// ------------------------------------------------------------------------------------------------ + +describe("Euler", () => { + + it("Fixed step size", () => { + fixed_step_size(Integrator.Euler, 0.1, f, "Euler-Fixed"); + }); + + it("Adaptive step size", () => { + adaptive_step_size(Integrator.Euler, 0.1, f, "Euler-Adaptive"); + }); +}) + +describe("Midpoint", () => { + it("Fixed step size", () => { + fixed_step_size(Integrator.Midpoint, 0.1, f, "Midpoint-Fixed"); + }); + + it("Adaptive step size", () => { + adaptive_step_size(Integrator.Midpoint, 0.1, f, "Midpoint-Adaptive"); }); +}); - it("Midpoint", () => { - let result = eval_func(Integrator.Midpoint, init_value); - let error = calculate_error(result, exact); - error.forEach((e, i) => { expect(e).toBeLessThan(max_error); }); - write_table(result, exact, error, "Midpoint", bins, 10); +describe("Runge-Kutta", () => { + it("Fixed step size", () => { + fixed_step_size(Integrator.Runge_Kutta, 0.1, f, "Runge-Kutta-Fixed"); }); - it("Runge-Kutta", () => { - let result = eval_func(Integrator.Runge_Kutta, init_value); - let error = calculate_error(result, exact); - error.forEach((e, i) => { expect(e).toBeLessThan(max_error); }); - write_table(result, exact, error, "Runge-Kutta", bins, 20); + it("Adaptive step size", () => { + adaptive_step_size(Integrator.Runge_Kutta, 0.1, f, "Runge-Kutta-Adaptive"); }); -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..590e673751256a24277dda9877d9f62701ef69ca --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true + }, + "include": ["src", "tests/Integrators.test.ts"] +}