This notebook is part of https://github.com/AudioSceneDescriptionFormat/splines, see also http://splines.readthedocs.io/.

# "Natural" End Conditions

For the first and last segment, we assume that the inner tangent is known.
We try to find the outer tangent by setting the second derivative to a fixed value (typically 0).

We are looking only at the non-uniform case, it's easy to get to the uniform case by setting $\Delta_0 = 1$.


```python
%matplotlib inline
```


```python
import sympy as sp
sp.init_printing(order='rev-lex')
```


```python
from utility import NamedExpression, NamedMatrix
```


```python
t, t0, t1 = sp.symbols('t t:2')
```


```python
delta = t1 - t0, sp.Symbol('Delta0')
sp.Eq(*delta)
```


```python
coefficients = sp.Matrix(sp.symbols('abm:4')[::-1])
coefficients
```


```python
sp.Matrix([t**3, t**2, t, 1]).dot(coefficients)
```


```python
p = _.subs(t, (t - t0) / (t1 - t0))
p.subs(*delta)
```

## Begin

\begin{align}
\boldsymbol{x}_0 &= \boldsymbol{p}(t_0)\\
\boldsymbol{x}_1 &= \boldsymbol{p}(t_1)\\
\boldsymbol{\dot{x}}_1 &= \boldsymbol{p}'(t_1)\\
\boldsymbol{\ddot{x}}_0 &= \boldsymbol{p}''(t_0)
\end{align}


```python
x0 = NamedExpression('xbm0', p.subs(t, t0))
x0
```


```python
x1 = NamedExpression('xbm1', p.subs(t, t1))
x1
```

Velocity = Tangent Vector = Derivative:


```python
velocity = p.diff(t)
velocity.subs(*delta)
```


```python
xd1 = NamedExpression('xdotbm1', velocity.subs(t, t1).simplify())
xd1.subs(*delta)
```

Acceleration = Second Derivative


```python
acceleration = p.diff(t, t)
acceleration.subs(*delta)
```


```python
xdd0 = NamedExpression('xddotbm0', acceleration.subs(t, t0))
xdd0.subs(*delta)
```


```python
control_values_begin = NamedMatrix(sp.Matrix([x0.name, x1.name, xd1.name, xdd0.name]))
```


```python
M_begin = NamedMatrix(r'{M_\text{begin}}', 4, 4)
```


```python
sp.Eq(coefficients, M_begin.name * control_values_begin.name)
```


```python
control_values_begin.expr = M_begin.name.I * coefficients
control_values_begin
```


```python
substitutions = x0, x1, xd1, xdd0
```


```python
control_values_begin.subs(substitutions).subs(*delta)
```


```python
M_begin.I = sp.Matrix([[expr.expand().coeff(cv).factor() for cv in coefficients]
                        for expr in control_values_begin.subs(substitutions).name])
M_begin.I.subs(*delta)
```


```python
M_begin.pull_out(sp.S.One / 4).subs(*delta)
```


```python
xd0 = NamedExpression('xdotbm0')
xd0.name
```


```python
control_values_H = NamedMatrix(sp.Matrix([x0.name, x1.name, xd0.name, xd1.name]))
control_values_H.name
```

from [notebook about non-uniform Hermite splines](hermite-non-uniform.ipynb)


```python
M_H = NamedMatrix(
    r'{M_\text{H}}',
    sp.S('Matrix([[2, -2, Delta0, Delta0], [-3, 3, -2*Delta0, -Delta0], [0, 0, Delta0, 0], [1, 0, 0, 0]])'))
M_H
```


```python
sp.Eq(coefficients, M_begin.name * control_values_begin.name)
```


```python
sp.Eq(coefficients, M_H.name * control_values_H.name)
```


```python
control_values_H.expr = M_H.I.name * M_begin.name * control_values_begin.name
control_values_H
```


```python
control_values_H = control_values_H.subs([M_H.I, M_begin]).doit().simplify()
control_values_H.subs(*delta)
```


```python
xd0.expr = control_values_H.expr[2]
xd0.subs(*delta)
```

if the second derivative is zero:


```python
xd0.subs(xdd0.name, 0).subs(*delta).simplify()
```

## End

\begin{align}
\boldsymbol{x}_0 &= \boldsymbol{p}(t_0)\\
\boldsymbol{x}_1 &= \boldsymbol{p}(t_1)\\
\boldsymbol{\dot{x}}_0 &= \boldsymbol{p}'(t_0)\\
\boldsymbol{\ddot{x}}_1 &= \boldsymbol{p}''(t_1)
\end{align}


```python
x0
```


```python
x1
```


```python
xd0 = NamedExpression('xdotbm0', velocity.subs(t, t0))
xd0.subs(*delta)
```


```python
xdd1 = NamedExpression('xddotbm1', acceleration.subs(t, t1).simplify())
xdd1.subs(*delta)
```


```python
control_values_end = NamedMatrix(sp.Matrix([x0.name, x1.name, xd0.name, xdd1.name]))
```


```python
M_end = NamedMatrix(r'{M_\text{end}}', 4, 4)
```


```python
control_values_end.expr = M_end.name.I * coefficients
control_values_end
```


```python
substitutions = x0, x1, xd0, xdd1
```


```python
M_end.I = sp.Matrix([[expr.expand().coeff(cv).factor() for cv in coefficients]
                     for expr in control_values_end.subs(substitutions).name])
M_end.I.subs(*delta)
```


```python
M_end.factor().subs(*delta).pull_out(sp.S.One / 4)
```


```python
control_values_H.expr = sp.simplify(M_H.I.expr * M_end.expr * control_values_end.name)
control_values_H.subs(*delta)
```


```python
xd1.expr = control_values_H.expr[3].simplify()
xd1.subs(*delta)
```

if the second derivative is supposed to be zero:


```python
xd1.subs(xdd1.name, 0).subs(*delta).simplify()
```

Luckily, that's the same as we got above,
just with $\boldsymbol{\dot{x}}_0$ and $\boldsymbol{\dot{x}}_1$ flipped.

## Example

one-dimensional; 3 time/value pairs are given.
The slope for the middle value is given, the begin and end slopes are calculated using the "natural" end condition.


```python
basis = sp.Matrix([t**3, t**2, t, 1]).T
```


```python
one, = basis * M_begin.expr * control_values_begin.name
two, = basis * M_end.expr * control_values_end.name
```


```python
one = one.subs(t, (t - t0) / (t1 - t0))
two = two.subs(t, (t - t0) / (t1 - t0))
```


```python
values = 2, 2, 2
times = 0, 4, 5
slope = 2
sp.plot((one.subs([(t0, times[0]), (t1, times[1]), (x0.name, values[0]), (x1.name, values[1]),
                   (xd1.name, slope), (xdd0.name, 0)]), (t, times[0], times[1])),
        (two.subs([(t0, times[1]), (t1, times[2]), (x0.name, values[1]), (x1.name, values[2]),
                   (xd0.name, slope), (xdd1.name, 0)]), (t, times[1], times[2])),
        axis_center=(0, values[1]));
```

93020d70b438687a6fe9b6119ecdd4af2ca1b97a
ipynb
Jupyter Notebook
doc/end-conditions-natural.ipynb
mgeier/splines
f54b09479d98bf13f00a183fd9d664b5783e3864
MIT
doc/end-conditions-natural.ipynb
mgeier/splines
f54b09479d98bf13f00a183fd9d664b5783e3864
MIT
doc/end-conditions-natural.ipynb
mgeier/splines
f54b09479d98bf13f00a183fd9d664b5783e3864
MIT
Qwen/Qwen-72B
1. YES
2. YES