Data Science 1 - Programmieren & Visualisieren
Wintersemester 2022/2023
Eine Demonstration der Taylor-Formel
viewof func = Inputs.radio(functions, {
value: functions[0],
format: d => tex`\displaystyle \large ${d.label()}`,
label: 'Wähle eine Funktion aus:',
})
// Show equation
renderSolution(func)
viewof degree = Inputs.range(
[0, 100],
{ value: 0, step: 1, label: 'Wähle den polynomialen Grad:' }
)
{
const f = sumOf(func.lambda, degree, func.n0)
let x = 0
const y = Math.max(func.range.interval[0], Math.min(func.range.interval[1], f(x)))
const xPastMiddle = Math.round(x * 1e5)/1e5 > d3.mean(func.domain.interval)
return Plot.plot({
margin: 40,
style: {
fontSize: 20,
padding: 20
},
marks: [
Plot.ruleY([0], { stroke: '#ddd' }),
taylorPlot,
benchmarkPlot,
Plot.dot(
[{x, y, type: 'func'}, {x, y: func.benchmark(x), type: 'benchmark'}],
{
x: 'x', y: 'y',
r: ({type}) => type === 'func',
fill: ({type}) => colors[type],
stroke: ({type}) => type === 'func' ? 'white' : 'none',
strokeWidth: 1.5,
opacity: ({type}) => type === 'func' ? 1/2 : 1,
}
)
],
width, height: Math.max(200, width / 2), marginTop: 32, marginBottom: 32,
x: { ...axisFor(func.domain), grid: true, label: 'x' },
y: { ...axisFor(func.range), label: 'f(x)' },
r: { range: [3, 8] },
})
}
functions = [
{
label: (x='x') => `e^{${x}}`,
benchmark: x => Math.pow(Math.E, x),
lambda: (x, n) => Math.pow(x, n) / factorial(n),
domain: {
interval: [-4, 4],
},
range: {
interval: [-Math.E, Math.pow(Math.E, 4)],
unit: 'e',
unitSize: Math.E
},
terms: {
formula: (x='x', n='n') => `\\frac {${x}^${n}} {${n}!}`,
sign: () => 1,
numeratorExp: n => n,
denominatorVal: n => n,
factorialize: true,
},
},
{
label: (x='x') => `\\ln(1+${x})`,
benchmark: x => Math.log(1 + x),
lambda: (x, n) => Math.pow(-1, n+1) * Math.pow(x, n) / n,
n0: 1,
domain: {
valid: [-1, 1],
interval: [-1.5, 1.5],
ticksPerUnit: 2,
},
range: {
interval: [-5, 1],
},
terms: {
formula: (x='x', n='n') => `(-1)^{${n}+1} \\frac {${x} ^ ${n}} {${n}}`,
sign: n => Math.pow(-1, n+1),
numeratorExp: n => n,
denominatorVal: n => n,
factorialize: false,
},
},
{
label: (x='x') => `\\cos(${x})`,
benchmark: Math.cos,
lambda: (x, n) => Math.pow(-1, n) * Math.pow(x, 2*n) / factorial(2*n),
domain: {
interval: [-3 * Math.PI, 3 * Math.PI],
unit: '𝜋',
unitSize: Math.PI,
},
range: {
interval: [-1.5, 1.5],
ticksPerUnit: 2,
},
terms: {
formula: (x='x', n='n') => `(-1)^${n} \\frac {${x} ^ {2${n}}} {(2${n})!}`,
sign: n => Math.pow(-1, n),
numeratorExp: n => 2*n,
denominatorVal: n => 2*n,
factorialize: true,
},
},
{
label: (x='x') => `\\sin(${x})`,
benchmark: Math.sin,
lambda: (x, n) => Math.pow(-1, n) * Math.pow(x, 2*n+1) / factorial(2*n+1),
domain: {
interval: [-3 * Math.PI, 3 * Math.PI],
unit: '𝜋',
unitSize: Math.PI,
},
range: {
interval: [-1.5, 1.5],
ticksPerUnit: 2,
},
terms: {
formula: (x='x', n='n') => `(-1)^${n} \\frac {${x} ^ {2${n}+1}} {(2${n} + 1)!}`,
sign: n => Math.pow(-1, n),
numeratorExp: n => 2*n + 1,
denominatorVal: n => 2*n + 1,
factorialize: true,
},
},
]
// Function to generate equations
function renderSolution(
{ label, n0, terms: { formula, sign, numeratorExp, denominatorVal, factorialize }, lambda, benchmark },
x = 'x', n1, realX
) {
n0 = n0 || 0
const smallScreen = width < 600
const series = d3.range(n0, Math.min((n1+1) || Infinity, smallScreen ? 4 : 6) + (n0))
.map(n => {
const denom = denominatorVal(n)
const exp = numeratorExp(n)
return [
denom === 1 || (factorialize && denom === 0) ? '' : '\\frac',
exp === 0 ? '{1}' : exp === 1 ? `{${x}}` : `{${x} ^ {${exp}}}`,
denom === 1 || (factorialize && denom === 0) ? '' : `{${denom}${factorialize ? '!' : ''}}`,
n1 != null && n === degree ? '' : sign(n+1) > 0 ? '+' : '-',
].join('')
})
const fmt = d3.format('.4')
const realSeries = realX != null && d3.range(n0, (n1 != null ? Math.min(n1, 20) : 4) + 1 + (n0))
.map(n => lambda(realX, n))
.map((v, i) => `${v >= 0 ? i === 0 ? '' : '+' : '-'}${fmt(Math.abs(v))}`)
.map(s => s.replace(/e(.*)/, `{\\times 10^{$1}}`))
const stripDoubleParens = s => s.replace(/\((\(.*\))\)/, '$1')
//const realBenchmark = benchmark(xSampleValue)
return html`<div style='font-size: ${smallScreen ? .7 : 1}em;'>
<div style='float: left; padding-top: .9em; margin-right: 8px;'>${
tex`\displaystyle \color{${colors.benchmark}} ${stripDoubleParens(`f(${x})`)} = ${stripDoubleParens(label(x))}`
}</div>
<div style='overflow: hidden;'>
${tex`\displaystyle
\color{${colors.func}} ${n1 ? '\\approx' : '='} \sum_{n=${n0}} ^ {${n1 === undefined ? '\\infty' : n1}} ${formula(x)} \newline
\color{${colors.series}} = ${series.join('')} ${n1 != null && series.length === degree+1 ? '' : '\\mathellipsis'} \newline
`}
</div>
</div>`
}
// Helper functions
taylorPlot = Plot.line(
plotPoints(sumOf(func.lambda, degree, func.n0), ...(func.domain.valid || func.domain.interval)),
{ stroke: colors.func, strokeWidth: 6, strokeOpacity: 2/3 }
)
benchmarkPlot = Plot.line(
plotPoints(func.benchmark, ...func.domain.interval),
{ stroke: colors.benchmark, strokeWidth: 2, strokeDasharray: '4 5' }
)
colors = ({
func: '#E2001A',
benchmark: '#0271BB',
series: '#555'
})
function plotPoints(fn, x0, x1) {
const numPoints = width / 4
return d3.range(x0, x1, (x1 - x0) / numPoints)
.map(x => [x, fn(x)])
}
function axisFor(config) {
const tickFormat = d3.format('.2')
return {
domain: config.interval,
ticks: d3.range(
config.interval[0],
config.interval[1] * (1 + 1e-9),
(config.unitSize || 1) / (config.ticksPerUnit || 1)
),
tickFormat: v => [
config.unit && Math.abs(v) === config.unitSize
? v < 0 ? '-' : ''
: tickFormat(v / (config.unitSize || 1)),
(v === 0 ? '' : config.unit),
].join('')
}
}
/* Returns f(x) such that when called with x, returns sigma of fn(x,n) as n goes from n0 to n1 */
function sumOf(fn, n1, n0=0) {
return function(x) {
return d3.sum(d3.range(n0, n1+1).map(n => fn(x, n)))
}
}
function factorial(n) {
if (!(n >= 0)) { throw new Error(`n must be greater than or equal to 0 (encountered n=${n})`) }
return n === 0 ? 1 : n * factorial(n - 1)
}
d3 = require('d3')