# 拟合 Fitting

xyfJASON

## 1 线性最小二乘法

给定平面上 $n$ 个点 $(x_i, y_i)$，求函数 $y=f(x)$，使得 $f(x)$ 与这些数据点最为接近。

基本思路：取一组 $[a,b]$ 上线性无关的函数 $\varphi_1(x),\varphi_2(x),\dots,\varphi_m(x)$，设 $\Phi=\text{Span}\{\varphi_1(x),\varphi_2(x),\ldots,\varphi_m(x)\}$，求解 $\varphi^*(x)\in\Phi$ 使得：

$$
\varphi^*(x)=\mathop{\arg\min}_{\varphi\in\Phi}\sum_{i=1}^n\left[y_i-\varphi(x_i)\right]^2
$$

设 $R=\begin{bmatrix}\varphi_1(x_1)&\cdots&\varphi_m(x_1)\\\vdots&\ddots&\vdots\\\varphi_1(x_n)&\cdots&\varphi_m(x_n)\\\end{bmatrix}\in\mathbb R^{n\times m}$，$A=\begin{bmatrix}a_1\\\vdots\\a_m\end{bmatrix}\in\mathbb R^{m}$，$Y=\begin{bmatrix}y_1\\\vdots\\y_n\end{bmatrix}\in\mathbb R^n$，求解该问题相当于求解：

$$
\min\quad J(a_1,a_2,\ldots,a_m)=\sum_{i=1}^n\left[y_i-\sum_{j=1}^ma_j\varphi_j(x_i)\right]^2=||RA-Y||_2^2
$$

求偏导并令其为零：

$$
\frac{\partial J}{\partial a_k}=2\sum_{i=1}^n\varphi_k(x_i)\left[\sum_{j=1}^ma_j\varphi_j(x_i)-y_i\right]=0,\quad k=1,2,\ldots,m
$$

解得：

$$
\sum_{j=1}^ma_j\left[\sum_{i=1}^n\varphi_k(x_i)\varphi_j(x_i)\right]=\sum_{i=1}^ny_i\varphi_k(x_i),\quad k=1,2,\ldots,m
$$

即：

$$
R^TRA=R^TY
$$

当 $\{\varphi_1(x),\varphi_2(x),\ldots,\varphi_m(x)\}$ 线性无关时，$R$ 列满秩，$R^TR$ 可逆，于是方程有唯一解：

$$
A=\left(R^TR\right)^{-1}R^TY
$$

## 2 线性最佳平方逼近

最佳平方逼近其实就是连续情况下的最小二乘法，推理与最小二乘法类似，仅需将求和改为积分。

如果定义内积：

$$
(\varphi_j,\varphi_k)=
\begin{cases}
\sum\limits_{i=1}^n\varphi_j(x_i)\varphi_k(x_i)&&\text{discrete}\\
\int_a^b\varphi_j(x)\varphi_i(x)\mathrm dx&&\text{continuous}
\end{cases}
$$

那么上一节的方程 $R^TRA=R^TY$ 可以写作：

$$
\begin{bmatrix}(\varphi_1,\varphi_1)&\cdots&(\varphi_1,\varphi_m)\\\vdots&\ddots&\vdots\\(\varphi_m,\varphi_1)&\cdots&(\varphi_m,\varphi_m)\end{bmatrix}\begin{bmatrix}a_1\\\vdots\\a_m\end{bmatrix}=\begin{bmatrix}(y,\varphi_1)\\\vdots\\(y,\varphi_m)\end{bmatrix}
$$

当 $\varphi_1,\varphi_2,\ldots,\varphi_m$ 线性无关时，上述矩阵非奇异，方程有唯一解。

## 3 代码

可以看到，所谓曲线拟合，最后其实都归约到了优化问题，只不过对于线性最小二乘/线性最佳平方逼近而言，我们可以直接得到这个优化问题的解析解。所以 `scipy` 中这部分的接口放在了 `scipy.optimize` 下。

### 3.1 线性最小二乘优化

使用 `scipy.optimize.lsq_linear`，其规定的标准形式为：

$$
\begin{align}
&\min\ \frac12||Ax-b||^2\\
&\text{s.t.}\ \mathrm{lb}\leqslant x\leqslant \mathrm{ub}
\end{align}
$$

Documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.lsq_linear.html

使用 `scipy.optimize.nnls` 可以解决无约束条件的非负解，其标准形式为：

$$
\mathop{\arg\min}_x\,||Ax-b||_2,\quad x\geqslant 0
$$

Documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.nnls.html

也可使用 `sklearn.linear_model.LinearRegression` 进行线性最小二乘回归，事实上从实现的角度看，该类仅是上述 `scipy` 接口的包装。

Documentation: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html

### 3.2 非线性最小二乘优化

使用 `scipy.optimize.least_squares`，其规定的标准形式为：

$$
\begin{align}
&\min\ F(x)=\frac12\times\sum_{i=0}^{n-1}J(f_i^2(x))\\
&\text{s.t.}\ \mathrm{lb}\leqslant x\leqslant\mathrm{rb}
\end{align}
$$

其中 $f_i$ 读入一个 $m$ 维向量 $x$（即我们要优化的参数），输出一个 $n$ 维向量 $f_i(x)$，例如在线性最小二乘中，$f_i$ 就是 $||A_{i,\bullet}x-b||$；

$J$ 是损失函数（例如线性、soft-l1 等等），线性损失 $J(x)=x$ 即原始的最小二乘；

$F$ 是代价函数，我们的目标是最小化代价函数。

Documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.least_squares.html

### 3.3 曲线拟合

`scipy.optimize.curve_fit` 使用非线性最小二乘优化拟合曲线。

Documentation: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html

输入用来拟合的（非）线性函数 `f(x, *params)`、数据点 $(x_i,y_i)$，输出拟合的参数 `params`。参数可有约束。

## 4 例题

### 4.1 例一

某乡镇企业 1990 年 - 1996 年的生产利润如下表所示：

| 年份      | 1990 | 1991 | 1992 | 1993 | 1994 | 1995 | 1996 |
| --------- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| 利润/万元 | 70   | 122  | 144  | 152  | 174  | 196  | 202  |

试预测 1997 年和 1998 年的利润。

作散点图观察，发现年生产利润几乎直线上升，因此可用 $y=a_0+a_1x$ 作拟合。

可以用 3 种计算方式，第一种是直接用第一节的结论计算，第二种是调用 `lsq_linear` 函数，第三种是调用 `curve_fit` 函数，代码如下：

```python
import numpy as np
from scipy.optimize import lsq_linear, curve_fit

x = np.array([1990, 1991, 1992, 1993, 1994, 1995, 1996])
y = np.array([70, 122, 144, 152, 174, 196, 202])
A = np.stack((np.ones(x.shape[0]), x), axis=1)
b = y

res = np.linalg.inv(A.T @ A) @ A.T @ y
print('#1:', res)

res = lsq_linear(A, b)
print('#2:', res)

res = curve_fit(lambda x, a0, a1: a0 + a1 * x, x, y)
print('#3:', res)
```

    #1: [-4.07050714e+04  2.05000000e+01]
    #2:  active_mask: array([0., 0.])
            cost: 419.3571428571963
             fun: array([ 19.92857143, -11.57142857, -13.07142857,  -0.57142857,
            -2.07142857,  -3.57142857,  10.92857143])
         message: 'The unconstrained solution is optimal.'
             nit: 0
      optimality: 4.353933036327362e-08
          status: 3
         success: True
               x: array([-4.07050714e+04,  2.05000000e+01])
    #3: (array([-4.07050714e+04,  2.05000000e+01]), array([[ 2.37958401e+07, -1.19396970e+04],
           [-1.19396970e+04,  5.99081633e+00]]))

可以看见 3 种方式计算结果是一致的，但第三种方式最为方便且应用面最为广泛。

### 4.2 例二

用 $y=ae^{bx}$ 拟合数据：

| x    | 1    | 2    | 3    | 4    | 5    | 6    | 7     | 8     |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ----- | ----- |
| y    | 15.3 | 20.5 | 27.4 | 36.6 | 49.1 | 65.6 | 87.87 | 117.6 |

直接拟合可以看作非线性问题，也可以取对数以后变为线性问题，代码如下：

```python
x = np.arange(1, 9)
y = np.array([15.3, 20.5, 27.4, 36.6, 49.1, 65.6, 87.87, 117.6])

res = curve_fit(lambda x, a, b: a * np.exp(b * x), x, y)
print(res)

res = curve_fit(lambda x, a, b: np.log(a) + b * x, x, np.log(y))
print(res)
```

    (array([11.4250665 ,  0.29142395]), array([[ 1.40603526e-04, -1.72314524e-06],
           [-1.72314524e-06,  2.21398094e-08]]))
    (array([11.4357665 ,  0.29126345]), array([[ 7.77920161e-05, -1.20044445e-06],
           [-1.20044445e-06,  2.33272888e-08]]))

从协方差矩阵可见，转化为线性问题之后误差稍小一些。


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, C.D. Cooper, G.F. Forsyth.

# Riding the wave

## Numerical schemes for hyperbolic PDEs

Welcome back! This is the second notebook of *Riding the wave: Convection problems*, the third module of  ["Practical Numerical Methods with Python"](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6286+2017/about).

The first notebook of this module discussed conservation laws and developed the non-linear traffic equation. We learned about the effect of the wave speed on the stability of the numerical method, and on the CFL number. We also realized that the forward-time/backward-space difference scheme really has many limitations: it cannot deal with wave speeds that move in more than one direction. It is also first-order accurate in space and time, which often is just not good enough. This notebook will introduce some new numerical schemes for conservation laws, continuing with the traffic-flow problem as motivation.

## Red light!

Let's explore the behavior of different numerical schemes for a moving shock wave. In the context of the traffic-flow model of the previous notebook, imagine a very busy road and a red light at $x=4$. Cars accumulate quickly in the front, where we have the maximum allowed density of cars between $x=3$ and $x=4$, and there is an incoming traffic of 50% the maximum allowed density $(\rho = 0.5\rho_{\rm max})$.

Mathematically, this is:

$$
\begin{equation}
\rho(x,0) = \left\{
\begin{array}{cc}
0.5 \rho_{\rm max} & 0 \leq x < 3  \\
\rho_{\rm max} & 3 \leq x \leq 4 \\
\end{array}
\right.
\end{equation}
$$

Let's find out what the initial condition looks like.

```python
import numpy
from matplotlib import pyplot
%matplotlib inline
```

```python
# Set the font family and size to use for Matplotlib figures.
pyplot.rcParams['font.family'] = 'serif'
pyplot.rcParams['font.size'] = 16
```

```python
def rho_red_light(x, rho_max):
    """
    Computes the "red light" initial condition with shock.

    Parameters
    ----------
    x : numpy.ndaray
        Locations on the road as a 1D array of floats.
    rho_max : float
        The maximum traffic density allowed.

    Returns
    -------
    rho : numpy.ndarray
        The initial car density along the road
        as a 1D array of floats.
    """
    rho = rho_max * numpy.ones_like(x)
    mask = numpy.where(x < 3.0)
    rho[mask] = 0.5 * rho_max
    return rho
```

```python
# Set parameters.
nx = 81  # number of locations on the road
L = 4.0  # length of the road
dx = L / (nx - 1)  # distance between two consecutive locations
nt = 40  # number of time steps to compute
rho_max = 10.0  # maximum taffic density allowed
u_max = 1.0  # maximum speed traffic

# Get the road locations.
x = numpy.linspace(0.0, L, num=nx)

# Compute the initial traffic density.
rho0 = rho_red_light(x, rho_max)
```

```python
# Plot the initial traffic density.
fig = pyplot.figure(figsize=(6.0, 4.0))
pyplot.xlabel(r'$x$')
pyplot.ylabel(r'$\rho$')
pyplot.grid()
line = pyplot.plot(x, rho0,
                   color='C0', linestyle='-', linewidth=2)[0]
pyplot.xlim(0.0, L)
pyplot.ylim(4.0, 11.0)
pyplot.tight_layout()
```

The question we would like to answer is: **How will cars accumulate at the red light?**

We will solve this problem using different numerical schemes, to see how they perform. These schemes are:

   * Lax-Friedrichs
   * Lax-Wendroff
   * MacCormack

Before we do any coding, let's think about the equation a little bit. The wave speed $u_{\rm wave}$ is $-1$ for $\rho = \rho_{\rm max}$ and $\rho \leq \rho_{\rm max}/2$, making all velocities negative. We should see a solution moving left, maintaining the shock geometry.

#### Figure 1. The exact solution is a shock wave moving left.

Now to some coding! First, let's define some useful functions and prepare to make some nice animations later.

```python
def flux(rho, u_max, rho_max):
    """
    Computes the traffic flux F = V * rho.

    Parameters
    ----------
    rho : numpy.ndarray
       Traffic density along the road as a 1D array of floats.
    u_max : float
        Maximum speed allowed on the road.
    rho_max : float
        Maximum car density allowed on the road.

    Returns
    -------
    F : numpy.ndarray
        The traffic flux along the road as a 1D array of floats.
    """
    F = rho * u_max * (1.0 - rho / rho_max)
    return F
```

Before we investigate different schemes, let's create the function to update the Matplotlib figure during the animation.

```python
from matplotlib import animation
from IPython.display import HTML
```

```python
def update_plot(n, rho_hist):
    """
    Update the line y-data of the Matplotlib figure.

    Parameters
    ----------
    n : integer
        The time-step index.
    rho_hist : list of numpy.ndarray objects
        The history of the numerical solution.
    """
    fig.suptitle('Time step {:0>2}'.format(n))
    line.set_ydata(rho_hist[n])
```

## Lax-Friedrichs scheme

Recall the conservation law for vehicle traffic, resulting in the following equation for the traffic density:

$$
\begin{equation}
\frac{\partial \rho}{\partial t} + \frac{\partial F}{\partial x} = 0
\end{equation}
$$

$F$ is the *traffic flux*, which in the linear traffic-speed model is given by:

$$
\begin{equation}
F = \rho u_{\rm max} \left(1-\frac{\rho}{\rho_{\rm max}}\right)
\end{equation}
$$

In the time variable, the natural choice for discretization is always a forward-difference formula; time invariably moves forward!

$$
\begin{equation}
\frac{\partial \rho}{\partial t}\approx \frac{1}{\Delta t}( \rho_i^{n+1}-\rho_i^n )
\end{equation}
$$

As is usual, the discrete locations on the 1D spatial grid are denoted by indices $i$ and the discrete time instants are denoted by indices $n$.

In a convection problem, using first-order discretization in space leads to excessive numerical diffusion (as you probably observed in [Lesson 1 of Module 2](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/02_spacetime/02_01_1DConvection.ipynb)). The simplest approach to get second-order accuracy in space is to use a central difference:

$$
\begin{equation}
\frac{\partial F}{\partial x} \approx \frac{1}{2\Delta x}( F_{i+1}-F_{i-1})
\end{equation}
$$

But combining these two choices for time and space discretization in the convection equation has catastrophic results! The "forward-time, central scheme" (FTCS) is **unstable**. (Go on: try it; you know you want to!)

The Lax-Friedrichs scheme was proposed by Lax (1954) as a clever trick to stabilize the forward-time, central scheme. The idea was to replace the solution value at $\rho^n_i$ by the average of the values at the neighboring grid points. If we do that replacement, we get the following discretized equation:

$$
\begin{equation}
\frac{\rho_i^{n+1}-\frac{1}{2}(\rho^n_{i+1}+\rho^n_{i-1})}{\Delta t} = -\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x}
\end{equation}
$$

Take a careful look: the difference formula no longer uses the value at $\rho^n_i$ to obtain $\rho^{n+1}_i$. The stencil of the Lax-Friedrichs scheme is slightly different than that for the forward-time, central scheme.

#### Figure 2. Stencil of the forward-time/central scheme.

#### Figure 3. Stencil of the Lax-Friedrichs scheme.

This numerical discretization is **stable**. Unfortunately, substituting  $\rho^n_i$ by the average of its neighbors introduces a first-order error. _Nice try, Lax!_

To implement the scheme in code, we need to isolate the value at the next time step, $\rho^{n+1}_i$, so we can write a time-stepping loop:

$$
\begin{equation}
\rho_i^{n+1} = \frac{1}{2}(\rho^n_{i+1}+\rho^n_{i-1}) - \frac{\Delta t}{2 \Delta x}(F^n_{i+1}-F^n_{i-1})
\end{equation}
$$

The function below implements Lax-Friedrichs for our traffic model.  All the schemes in this notebook are wrapped in their own functions to help with displaying animations of the results.  This is also good practice for developing modular, reusable code.

In order to display animations, we're going to hold the results of each time step in the variable `rho`, a 2D array.  The resulting array `rho_n` has `nt` rows and `nx` columns.

```python
def lax_friedrichs(rho0, nt, dt, dx, bc_values, *args):
    """
    Computes the traffic density on the road
    at a certain time given the initial traffic density.
    Integration using Lax-Friedrichs scheme.

    Parameters
    ----------
    rho0 : numpy.ndarray
        The initial traffic density along the road
        as a 1D array of floats.
    nt : integer
        The number of time steps to compute.
    dt : float
        The time-step size to integrate.
    dx : float
        The distance between two consecutive locations.
    bc_values : 2-tuple of floats
        The value of the density at the first and last locations.
    args : list or tuple
        Positional arguments to be passed to the flux function.

    Returns
    -------
    rho_hist : list of numpy.ndarray objects
        The history of the car density along the road.
    """
    rho_hist = [rho0.copy()]
    rho = rho0.copy()
    for n in range(nt):
        # Compute the flux.
        F = flux(rho, *args)
        # Advance in time using Lax-Friedrichs scheme.
        rho[1:-1] = (0.5 * (rho[:-2] + rho[2:]) -
                     dt / (2.0 * dx) * (F[2:] - F[:-2]))
        # Set the value at the first location.
        rho[0] = bc_values[0]
        # Set the value at the last location.
        rho[-1] = bc_values[1]
        # Record the time-step solution.
        rho_hist.append(rho.copy())
    return rho_hist
```

### Lax-Friedrichs with $\frac{\Delta t}{\Delta x}=1$

We are now all set to run! First, let's try with CFL=1

```python
# Set the time-step size based on CFL limit.
sigma = 1.0
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = lax_friedrichs(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                          u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

##### Think

* What do you see in the animation above? How does the numerical solution compare with the exact solution (a left-traveling shock wave)?
* What types of errors do you think we see?
* What do you think of the Lax-Friedrichs scheme, so far?

### Lax-Friedrichs with $\frac{\Delta t}{\Delta x} = 0.5$

Would the solution improve if we use smaller time steps? Let's check that!

```python
# Set the time-step size based on CFL limit.
sigma = 0.5
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = lax_friedrichs(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                          u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

##### Dig deeper

Notice the strange "staircase" behavior on the leading edge of the wave?  You may be interested to learn more about this: a feature typical of what is sometimes called "odd-even decoupling." Last year we published a collection of lessons in Computational Fluid Dynamics, called _CFD Python_, where we discuss [odd-even decoupling](https://nbviewer.jupyter.org/github/barbagroup/CFDPython/blob/14b56718ac1508671de66bab3fe432e93cb59fcb/lessons/19_Odd_Even_Decoupling.ipynb).

* How does this solution compare with the previous one, where the Courant number was $\frac{\Delta t}{\Delta x}=1$?

## Lax-Wendroff scheme

The Lax-Friedrichs method uses a clever trick to stabilize the central difference in space for convection, but loses an order of accuracy in doing so. First-order methods are just not good enough for convection problems, especially when you have sharp gradients (shocks).

The Lax-Wendroff (1960) method was the _first_ scheme ever to achieve second-order accuracy in both space and time. It is therefore a landmark in the history of computational fluid dynamics.

To develop the Lax-Wendroff scheme, we need to do a bit of work. Sit down, grab a notebook and grit your teeth. We want you to follow this derivation in your own hand. It's good for you! Start with the Taylor series expansion (in the time variable) about $\rho^{n+1}$:

$$
\begin{equation}
\rho^{n+1} = \rho^n + \frac{\partial\rho^n}{\partial t} \Delta t + \frac{(\Delta t)^2}{2}\frac{\partial^2\rho^n}{\partial t^2} + \ldots
\end{equation}
$$

For the conservation law with $F=F(\rho)$, and using our beloved chain rule, we can write:

$$
\begin{equation}
\frac{\partial \rho}{\partial t} = -\frac{\partial F}{\partial x} = -\frac{\partial F}{\partial \rho} \frac{\partial \rho}{\partial x} = -J \frac{\partial \rho}{\partial x}
\end{equation}
$$

where

$$
\begin{equation}
J = \frac{\partial F}{\partial \rho} = u _{\rm max} \left(1-2\frac{\rho}{\rho_{\rm max}} \right)
\end{equation}
$$

is the _Jacobian_ for the traffic model. Next, we can do a little trickery:

$$
\begin{equation}
\frac{\partial F}{\partial t} = \frac{\partial F}{\partial \rho} \frac{\partial \rho}{\partial t} = J \frac{\partial \rho}{\partial t} = -J \frac{\partial F}{\partial x}
\end{equation}
$$

In the last step above, we used the differential equation of the traffic model to replace the time derivative by a spatial derivative. These equivalences imply that

$$
\begin{equation}
\frac{\partial^2\rho}{\partial t^2} = \frac{\partial}{\partial x} \left( J \frac{\partial F}{\partial x} \right)
\end{equation}
$$

Let's use all this in the Taylor expansion:

$$
\begin{equation}
\rho^{n+1} = \rho^n - \frac{\partial F^n}{\partial x} \Delta t + \frac{(\Delta t)^2}{2} \frac{\partial}{\partial x} \left(J\frac{\partial F^n}{\partial x} \right)+ \ldots
\end{equation}
$$

We can now reorganize this and discretize the spatial derivatives with central differences to get the following discrete equation:

$$
\begin{equation}
\frac{\rho_i^{n+1} - \rho_i^n}{\Delta t} = -\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x} + \frac{\Delta t}{2} \left(\frac{(J \frac{\partial F}{\partial x})^n_{i+\frac{1}{2}}-(J \frac{\partial F}{\partial x})^n_{i-\frac{1}{2}}}{\Delta x}\right)
\end{equation}
$$

Now, approximate the rightmost term (inside the parenthesis) in the above equation as follows:
\begin{equation} \frac{J^n_{i+\frac{1}{2}}\left(\frac{F^n_{i+1}-F^n_{i}}{\Delta x}\right)-J^n_{i-\frac{1}{2}}\left(\frac{F^n_i-F^n_{i-1}}{\Delta x}\right)}{\Delta x}\end{equation}

Then evaluate the Jacobian at the midpoints by using averages of the points on either side:

\begin{equation}\frac{\frac{1}{2 \Delta x}(J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-\frac{1}{2 \Delta x}(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})}{\Delta x}.\end{equation}

Our equation now reads:

\begin{align}
&\frac{\rho_i^{n+1} - \rho_i^n}{\Delta t} =
-\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x} + \cdots \\ \nonumber
&+ \frac{\Delta t}{4 \Delta x^2} \left( (J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})\right)
\end{align}

Solving for $\rho_i^{n+1}$:

\begin{align}
&\rho_i^{n+1} = \rho_i^n - \frac{\Delta t}{2 \Delta x} \left(F^n_{i+1}-F^n_{i-1}\right) + \cdots \\ \nonumber
&+ \frac{(\Delta t)^2}{4(\Delta x)^2} \left[ (J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})\right]
\end{align}

with

\begin{equation}J^n_i = \frac{\partial F}{\partial \rho} = u_{\rm max} \left(1-2\frac{\rho^n_i}{\rho_{\rm max}} \right).\end{equation}

Lax-Wendroff is a little bit long.  Remember that you can use \ slashes to split up a statement across several lines.  This can help make code easier to parse (and also easier to debug!).

```python
def jacobian(rho, u_max, rho_max):
    """
    Computes the Jacobian for our traffic model.

    Parameters
    ----------
    rho : numpy.ndarray
       Traffic density along the road as a 1D array of floats.
    u_max : float
        Maximum speed allowed on the road.
    rho_max : float
        Maximum car density allowed on the road.

    Returns
    -------
    J : numpy.ndarray
        The Jacobian as a 1D array of floats.
    """
    J = u_max * (1.0 - 2.0 * rho / rho_max)
    return J
```

```python
def lax_wendroff(rho0, nt, dt, dx, bc_values, *args):
    """
    Computes the traffic density on the road
    at a certain time given the initial traffic density.
    Integration using Lax-Wendroff scheme.

    Parameters
    ----------
    rho0 : numpy.ndarray
        The initial traffic density along the road
        as a 1D array of floats.
    nt : integer
        The number of time steps to compute.
    dt : float
        The time-step size to integrate.
    dx : float
        The distance between two consecutive locations.
    bc_values : 2-tuple of floats
        The value of the density at the first and last locations.
    args : list or tuple
        Positional arguments to be passed to the
        flux and Jacobien functions.

    Returns
    -------
    rho_hist : list of numpy.ndarray objects
        The history of the car density along the road.
    """
    rho_hist = [rho0.copy()]
    rho = rho0.copy()
    for n in range(nt):
        # Compute the flux.
        F = flux(rho, *args)
        # Compute the Jacobian.
        J = jacobian(rho, *args)
        # Advance in time using Lax-Wendroff scheme.
        rho[1:-1] = (rho[1:-1] -
                     dt / (2.0 * dx) * (F[2:] - F[:-2]) +
                     dt**2 / (4.0 * dx**2) *
                     ((J[1:-1] + J[2:]) * (F[2:] - F[1:-1]) -
                      (J[:-2] + J[1:-1]) * (F[1:-1] - F[:-2])))
        # Set the value at the first location.
        rho[0] = bc_values[0]
        # Set the value at the last location.
        rho[-1] = bc_values[1]
        # Record the time-step solution.
        rho_hist.append(rho.copy())
    return rho_hist
```

Now that's we've defined a function for the Lax-Wendroff scheme, we can use the same procedure as above to animate and view our results.

### Lax-Wendroff with $\frac{\Delta t}{\Delta x}=1$

```python
# Set the time-step size based on CFL limit.
sigma = 1.0
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = lax_wendroff(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                        u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

Interesting! The Lax-Wendroff method captures the sharpness of the shock much better than the Lax-Friedrichs scheme, but there is a new problem: a strange wiggle appears right at the tail of the shock. This is typical of many second-order methods: they introduce _numerical oscillations_ where the solution is not smooth. Bummer.

### Lax-Wendroff with $\frac{\Delta t}{\Delta x} =0.5$

How do the oscillations at the shock front vary with changes to the CFL condition? You might think that the solution will improve if you make the time step smaller ... let's see.

```python
# Set the time-step size based on CFL limit.
sigma = 0.5
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = lax_wendroff(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                        u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

Eek! The numerical oscillations got worse. Double bummer!

Why do we observe oscillations with second-order methods? This is a question of fundamental importance!

## MacCormack Scheme

The numerical oscillations that you observed with the Lax-Wendroff method on the traffic model can become severe in some problems. But actually the main drawback of the Lax-Wendroff method is having to calculate the Jacobian in every time step. With more complicated equations (like the Euler equations), calculating the Jacobian is a large computational expense.

Robert W. MacCormack introduced the first version of his now-famous method at the 1969 AIAA Hypervelocity Impact Conference, held in Cincinnati, Ohio, but the paper did not at first catch the attention of the aeronautics community. The next year, however, he presented at the 2nd International Conference on Numerical Methods in Fluid Dynamics at Berkeley. His paper there (MacCormack, 1971) was a landslide. MacCormack got a promotion and continued to work on applications of his method to the compressible Navier-Stokes equations. In 1973, NASA gave him the prestigious H. Julian Allen award for his work.

The MacCormack scheme is a two-step method, in which the first step is called a _predictor_ and the second step is called a _corrector_. It achieves second-order accuracy in both space and time. One version is as follows:

$$
\begin{equation}
\rho^*_i = \rho^n_i - \frac{\Delta t}{\Delta x} (F^n_{i+1}-F^n_{i}) \ \ \ \ \ \ \text{(predictor)}
\end{equation}
$$

$$
\begin{equation}
\rho^{n+1}_i = \frac{1}{2} (\rho^n_i + \rho^*_i - \frac{\Delta t}{\Delta x} (F^*_i - F^{*}_{i-1})) \ \ \ \ \ \ \text{(corrector)}
\end{equation}
$$

If you look closely, it appears like the first step is a forward-time/forward-space scheme, and the second step is like a forward-time/backward-space scheme (these can also be reversed), averaged with the first result. What is so cool about this? You can compute problems with left-running waves and right-running waves, and the MacCormack scheme gives you a stable method (subject to the CFL condition). Nice! Let's try it.

```python
def maccormack(rho0, nt, dt, dx, bc_values, *args):
    """
    Computes the traffic density on the road
    at a certain time given the initial traffic density.
    Integration using MacCormack scheme.

    Parameters
    ----------
    rho0 : numpy.ndarray
        The initial traffic density along the road
        as a 1D array of floats.
    nt : integer
        The number of time steps to compute.
    dt : float
        The time-step size to integrate.
    dx : float
        The distance between two consecutive locations.
    bc_values : 2-tuple of floats
        The value of the density at the first and last locations.
    args : list or tuple
        Positional arguments to be passed to the flux function.

    Returns
    -------
    rho_hist : list of numpy.ndarray objects
        The history of the car density along the road.
    """
    rho_hist = [rho0.copy()]
    rho = rho0.copy()
    rho_star = rho.copy()
    for n in range(nt):
        # Compute the flux.
        F = flux(rho, *args)
        # Predictor step of the MacCormack scheme.
        rho_star[1:-1] = (rho[1:-1] -
                          dt / dx * (F[2:] - F[1:-1]))
        # Compute the flux.
        F = flux(rho_star, *args)
        # Corrector step of the MacCormack scheme.
        rho[1:-1] = 0.5 * (rho[1:-1] + rho_star[1:-1] -
                           dt / dx * (F[1:-1] - F[:-2]))
        # Set the value at the first location.
        rho[0] = bc_values[0]
        # Set the value at the last location.
        rho[-1] = bc_values[1]
        # Record the time-step solution.
        rho_hist.append(rho.copy())
    return rho_hist
```

### MacCormack with $\frac{\Delta t}{\Delta x} = 1$

```python
# Set the time-step size based on CFL limit.
sigma = 1.0
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = maccormack(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                      u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

### MacCormack with $\frac{\Delta t}{\Delta x}= 0.5$

Once again, we ask: how does the CFL number affect the errors? Which one gives better results? You just have to try it.

```python
# Set the time-step size based on CFL limit.
sigma = 0.5
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = maccormack(rho0, nt, dt, dx, (rho0[0], rho0[-1]),
                      u_max, rho_max)
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

##### Dig Deeper

You can also obtain a MacCormack scheme by reversing the predictor and corrector steps. For shocks, the best resolution will occur when the difference in the predictor step is in the direction of propagation. Try it out! Was our choice here the ideal one? In which case is the shock better resolved?

##### Challenge task

In the *red light* problem, $\rho \geq \rho_{\rm max}/2$, making the wave speed negative at all points . You might be wondering why we introduced these new methods; couldn't we have just used a forward-time/forward-space scheme? But, what if $\rho_{\rm in} < \rho_{\rm max}/2$? Now, a whole region has negative wave speeds and forward-time/backward-space is unstable.

* How do Lax-Friedrichs, Lax-Wendroff and MacCormack behave in this case? Try it out!

* As you decrease $\rho_{\rm in}$, what happens to the velocity of the shock? Why do you think that happens?

## References

* Peter D. Lax (1954), "Weak solutions of nonlinear hyperbolic equations and their numerical computation," _Commun. Pure and Appl. Math._, Vol. 7, pp. 159–193.

* Peter D. Lax and Burton Wendroff (1960), "Systems of conservation laws,"  _Commun. Pure and Appl. Math._, Vol. 13, pp. 217–237.

* R. W. MacCormack (1969), "The effect of viscosity in hypervelocity impact cratering," AIAA paper 69-354. Reprinted on _Journal of Spacecraft and Rockets_, Vol. 40, pp. 757–763 (2003). Also on _Frontiers of Computational Fluid Dynamics_, edited by D. A. Caughey, M. M. Hafez (2002), chapter 2: [read on Google Books](http://books.google.com/books?id=QBsnMOz_8qcC&lpg=PA27&ots=uqCeuH1U6S&lr&pg=PA27#v=onepage&q&f=false).

* R. W. MacCormack (1971), "Numerical solution of the interaction of a shock wave with a laminar boundary layer," _Proceedings of the 2nd Int. Conf. on Numerical Methods in Fluid Dynamics_, Lecture Notes in Physics, Vol. 8, Springer, Berlin, pp. 151–163.

---

###### The cell below loads the style of the notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "Source Code Pro";
    font-size: 90%;
}

.alert-box {
    padding:10px 10px 10px 36px;
    margin:5px;
}

.success {
    color:#666600;
    background:rgb(240,242,229);
}
</style>


## Общая физика. Механика. Лекция 2. Кинематика материальной точки.

*(c) 2021 Alexey Shcherbakov. These lectures are licensed under a Creative Commons Attribution License CC-BY 4.0. All code contained herein is licensed under an MIT license. The lectures were generated from a Jupyter notebook.*

### План лекции:
* основные понятия
* ускорение материальной точки
* радиус кривизны траектории
* баллистическое движение
* движение по окружности
* циклоида

### Основные понятия
В начале курса мы будем иметь дело с простыми случаями и рассматривать движение материальных точек - таких физических объектов, размерами которых в условиях задачи можно пренебречь. Говоря о кинематике, мы подразумеваем, что изучаются закономерности движения без выяснения причин, по которым это движение происходит. Напомним основные понятия, известные из школьного курса общей физики, относящиеся к кинематике материальной точки.

Для задания положения материальной точки в пространстве нужно ввести систему отсчета - физческое тело и привязанную к нему систему координат, например, декартову. В общем случае введение обобщенных координат подразумевает использование минимального числа независимых параметров, которые однозначно определяют положение тела в пространстве (в двумерном пространстве - две координаты, в трехмерном - три и т.п.). Например, полярные координаты $r,\;\varphi$ на плоскости или сферические координаты $r,\;\theta,\;\varphi$ в пространстве.

Положение материальной точки в заданной системе отсчета можно по-разному. Можно ввести зависимый от времени радиус-вектор ${\bf r}(t)$, или зафиксировать траекторию и отсчитывать путь, пройденный по этой траектории от некоторой начальной точки $s=s(t)$. Смещением будет вектор
$$\Delta{\bf r}(t) = \Delta{\bf r}(t) - \Delta{\bf r}(0)$$
причем, очевидно, что $|\Delta{\bf r}(t)| \leq s(t)$. Вектор скорости ${\bf v}(t)$ всегда направлен по касательной к траектории.

Траектория, перемещение, путь и вектор скорости материальной точки

### Ускорение материальной точки
Рассмотрим декартову систему координат и зададим положение материальной точки радиус-вектором
$${\bf r}\left(t\right) = x\hat{{\bf e}}_{x}+y\hat{{\bf e}}_{y}+z\hat{{\bf e}}_{z} = \left(x\left(t\right),y\left(t\right),z\left(t\right)\right)$$
Скорость есть производная радиус-вектора по времени:
$${\bf v} = \dfrac{d{\bf r}}{dt} = \dfrac{dx}{dt}\hat{{\bf e}}_{x}+\dfrac{dy}{dt}\hat{{\bf e}}_{y}+\dfrac{dz}{dt}\hat{{\bf e}}_{z} = v_{x}\hat{{\bf e}}_{x}+v_{y}\hat{{\bf e}}_{y}+v_{z}\hat{{\bf e}}_{z}$$
Ускорение – это скорость изменения скорости:
$${\bf a}=\dfrac{d{\bf v}}{dt}=\dfrac{dv_{x}}{dt}\hat{{\bf e}}_{x}+\dfrac{dv_{y}}{dt}\hat{{\bf e}}_{y}+\dfrac{dv_{z}}{dt}\hat{{\bf e}}_{z}=a_{x}\hat{{\bf e}}_{x}+a_{y}\hat{{\bf e}}_{y}+a_{z}\hat{{\bf e}}_{z}=\dfrac{d^{2}{\bf r}}{dt^{2}}$$

Обозначим путь вдоль траектории как $s$. Его еще называют естественным параметром движения, поскольку он задает положение материальной точки на траектории, отсчитываемое от некоторой начальной точки. Введем единичный вектор касательной к траектории $\hat{\tau} = d{\bf r}/ds$. Тогда скорость можно выразить как
$${\bf v} = \frac{d{\bf r}}{dt} = \frac{d{\bf r}}{ds}\frac{ds}{dt} = v\left(t\right)\hat{\tau}\left(t\right)$$
а, зная скорость, путь будет вычисляться через интеграл
$$v(t) = \frac{ds}{dt} \; \Rightarrow\;s=\intop_{t_{1}}^{t_{2}}v\left(t\right)dt$$

В общем случае движения материальной точки по криволинейной траектории в каждой точке траектории (при определенных математических ограничениях) можно ввести вектор касательной $\hat{\tau}$ и вектор нормали $\hat{\bf n}$. Оказывается, что ускорение материальной точки раскладывается по этим двум векторам:
$${\bf a}=\dfrac{d{\bf v}}{dt}=\dfrac{d}{dt}\left(v\hat{\tau}\right)=\dfrac{dv}{dt}\hat{\tau}+v\dfrac{d\hat{\tau}}{dt}=$$
$$=a_{\tau}\hat{\tau}+v\dfrac{d{\bf \tau}}{ds}\dfrac{ds}{dt}=a_{\tau}\hat{\tau}+v^{2}\dfrac{d\hat{\tau}}{ds}=a_{\tau}\hat{\tau}+\dfrac{v^{2}}{R}\hat{\bf n}$$

Чтобы получить значение производной $d\hat{\tau}/ds$ можно рассмотреть конечное приращение вектора $\hat{\tau}$ и ввести окружность, касательную в данной точке траектории (движение материальной точки в малой области представляется как движение по окружности):
$$|\Delta\hat{\bf \tau}| = 2|\hat{\bf \tau}|\sin(\alpha/2) = 2|\hat{\bf \tau}|\frac{|\Delta{\bf r}|/2}{R}$$
$$\Rightarrow \frac{d\hat{\bf \tau}}{ds} = \hat{\bf n} \lim_{\Delta s\rightarrow 0} \frac{|\Delta\hat{\bf \tau}|}{\Delta s} = \hat{\bf n} \lim_{\Delta s\rightarrow 0} \frac{|\Delta\hat{\bf \tau}|}{|\Delta {\bf r}|} = \frac{1}{R}\hat{\bf n}$$

К выводу нормальной компоненты вектора ускорения

### Частные случаи

Рассмотрим некоторые частные случаи, хорошо известные из школько курса физики.

1. При одномерном движении начальные условия определяют неизвестные константы интегрирования: $$v_{x}\left(t\right)=\dfrac{dx}{dt}=\int adt=at+C_{1}\Rightarrow x\left(t\right)=\int\left(at+C_{1}\right)dt=\dfrac{1}{2}at^{2}+C_{1}t+C_{2}$$ $$x\left(0\right)=x_{0},v\left(0\right)=v_{0}\Rightarrow C_{1}=v_{0},C_{2}=x_{0}$$
2. При движении тел, брошенных под углом к горизонту, $$\begin{array}{c} a_{x}=0\cos\varphi\\ a_{y}=-g \end{array} \Rightarrow \begin{array}{c} v_{x}=v_{0}\cos\varphi\\ v_{y}=v_{0}\sin\varphi-gt \end{array} \Rightarrow \begin{array}{c}
x=x_{0}+v_{0}t\cos\varphi\\
y=y_{0}+v_{0}t\sin\varphi-\dfrac{1}{2}gt^{2}
\end{array}$$ Условие касания земли дает полное время полета: $$y=0\Rightarrow v_{0}t\sin\varphi-\dfrac{1}{2}gt^{2}=0\Rightarrow T=2\dfrac{v_{0}}{g}\sin\varphi$$ Дальность полета и высота: $$\begin{array}{c}
L=x\left(T\right)=\dfrac{v_{0}^{2}}{g}\sin2\varphi\\
H=y\left(T/2\right)=\dfrac{1}{2}\dfrac{v_{0}^{2}}{g}\sin^{2}\varphi
\end{array}$$ Максимальную дальность и высоту можно найти из условия экстремальности: $$\begin{array}{c}
\dfrac{dL}{d\varphi}=2\dfrac{v_{0}^{2}}{g}\cos2\varphi=0\Rightarrow\varphi=\pi/4\\
\dfrac{dH}{d\varphi}=\dfrac{v_{0}^{2}}{2g}\sin2\varphi=0\Rightarrow\varphi=\pi/2
\end{array}$$
3. При равномерном движении по окружности можно ввести угловую скорость $$\omega = \dfrac{d\varphi}{dt}$$ так что $$ds=Rd\varphi\Rightarrow\dfrac{ds}{dt}=R\dfrac{d\varphi}{dt}\Rightarrow v=R\omega$$

### Циклоида
Часто, если материальная точки совершает сложное движение, его можно представить как суперпозицию более простых. Наглядным примером является движение точек на ободе колеса. Если колесо радиуса $R$ катится без проскальзывания, то скорость нижней точки касания с землей равна нулю, а скорость центра колеса связана с угловой скоростью вращения как $v=\omega R$. Положение произвольной точки обода есть сумма радиус-вектора центра колеса и вектора, задающего положение относительно центра, а скорость, соответственно, есть сумма скоростей центра и скорости относительно центра:
$${\bf r}={\bf R}_{c}+{\bf r}_1$$
$${\bf v}={\bf v}_{c}+\omega R\hat{\bf \tau}$$
При равномерном качении ${\bf R}_c = {\bf V}t$. Если отсчитывать угол поворота колеса относительно вертикали $\varphi = \omega t$, то в проекции на оси декартовой системы координат получается параметрическое уравнение траектории:
$$x = R \left( \omega t + \sin\left(\omega t\right)\right)$$
$$y = R \left(1 + \cos\left(\omega t\right)\right)$$
Эта кривая называется циклоидой. Одно из ее свойств: если ее перевернуть и сделать скат такой же формы, то время соскальзывания (без трения) малых тел с такого ската не будет зависеть от начального положения тела. Такая кривая еще называется изохронной или таутохронной. Другое свойства этой кривой – тела по ней будут съезжать быстрее всего (брахистрохрона). Гюйгенс предложил маятник, движущийся по огибаюдей циклоиды, период которого не зависит от амплитуды колебанийй:

```python
# брахистрона:
from IPython.display import YouTubeVideo
YouTubeVideo('1BdO8J0iynY', width=800, height=300)
```

```python
# маятник Гюйгенса:
from IPython.display import YouTubeVideo
YouTubeVideo('EI5zwTvfkqA', width=800, height=300)
```

```python
# построим циклоиду:
from sympy import *
from sympy.plotting import plot_parametric
import math
import matplotlib.pyplot as plt
t = symbols('t')
x = t-sin(t)
y = 1-cos(t)
plt.rcParams['figure.figsize'] = 13, 2.5
plot_parametric(x, y, (t, 0, 4*pi), xlim=[0,13], ylim=[0,2.5])
```

### Радиус кривизны траектории

При выводе общей формулы разложения ускорения мы воспользовались возможностью приблизить траекторию на достаточно малом участке окружностью некоторого радиуса. Рассмотрим частный случай плоского движения и найдем этот радиус, если известна траектория движения как функция $y = y(x)$. Выразим малое приращение пути с одной стороны через малое приращение угла при движении по касательной окружности, а с другой - через малые приращения координат:
$$ds = Rd\alpha$$
$$ds = \sqrt{\left(dx\right)^{2} + \left(dy\right)^{2}} = dx \sqrt{1 + \left(\dfrac{dy}{dx}\right)^{2}} = dx\sqrt{1+\left(y'\right)^{2}}\Rightarrow\dfrac{d\alpha}{dx} = \dfrac{1}{R}\sqrt{1+\left(y'\right)^{2}}$$
Вторая производная функции, задающей траекторию,
$$y'\equiv\dfrac{dy}{dx}=\tan\alpha\Rightarrow y''=\dfrac{d}{dx}\left(\tan\alpha\right)=\dfrac{1}{\cos^{2}\alpha}\dfrac{d\alpha}{dx}=\left(1+\tan^{2}\alpha\right)\dfrac{d\alpha}{dx}=\left(1+\left(y'\right)^{2}\right)\dfrac{d\alpha}{dx}$$
Тогда подставляя производную $d\alpha/dx$ из второго выражения в первое, получаем,
$$\dfrac{d\alpha}{dx}=\dfrac{1}{R}\sqrt{1+\left(y'\right)^{2}}=\dfrac{y''}{1+\left(y'\right)^{2}}\Rightarrow R=\dfrac{\left(1+\left(y'\right)^{2}\right)^{3/2}}{y''}$$

К выводу формулы радиуса кривизны траектории

Найдем радиус кривизны трактории при баллистическом движении материальной точки. С одной стороны, можно использовать выведенное выражение, подставив уравнение траектории - параболу. С другой, можно воспользоваться фактом о том, что полное ускорение тела в любой точке траектории равно $\bf g$, а скорость направлена по касательной. Зная проекции скорости в некоторый момент времени
$$\begin{array}{c}
v_{x}\left(t_{0}\right)=v_{0}\cos\varphi\\
v_{y}\left(t_{0}\right)=v_{0}\sin\varphi-gt_{0}
\end{array}$$
можно выразить угол между вектором скорости и вертикалью:
$$\alpha(t_0) = \arccos\frac{v_{x}(t_0)}{v(t_0)}$$
Тогда проекция полного ускорения на нормаль к тректории
$$a_{n} = g\cos\alpha = g\dfrac{v_{x}}{v}$$
где $v\left(t_{0}\right)=\sqrt{v_{x}^{2}+v_{y}^{2}}=\sqrt{v_{0}^{2}+g^{2}t_{0}^{2}-2v_{0}gt_{0}\sin\varphi}$ - полная скорость. С другой стороны, в общем случае $a_n = v^2/R$, откуда
$$R=\dfrac{v^{2}}{a_{n}}=\dfrac{v^{3}}{gv_{x}}.$$

К выводу формулы радиуса кривизны баллистической траектории

```python

```


# Acoplamento eletroacústico (alto-falante no baffle infinito)

O circuito eletromecânico do sistema é dado na figura.

Se conseguirmos mensurar a velocidade de vibração do pistão, $\tilde{\dot{X}}$, podemos usar a Eq. (1) para mensurar a pressão sonora radiada na direção ***normal*** e a uma distância $r$ do pistão.

\begin{equation}
\tilde{P}(r,0^{\circ})=\text{j} 2 \rho_{0} c_{0} \tilde{\dot{X}} \ \sin\left[\frac{k_{0}}{2}\left(\sqrt{r^{2}+a^{2}}-r\right)\right] \ \text{e}^{-\text{j} (k_{0}/2) (\sqrt{r^{2}+a^{2}}+r)}
\tag{1}
\end{equation}

```python
import numpy as np
from scipy import special
from matplotlib import pyplot as plt
plt.rcParams.update({'font.size': 14})

def zrad_piston_infbaffle(a, freq, rho0 = 1.21, c0 = 343):
    # Equivalent circuit
    Ra1 = (0.1404 * rho0 * c0) / (a**2)
    Ra2 = (rho0 * c0)/(np.pi * a**2)
    Ca1 = 5.94 *a**3/(rho0 * c0**2)
    Ma1 = (0.27 * rho0) / a
    # Equivalent impedance
    omega = 2*np.pi*freq
    Zrad_eq=(-(omega**2)*Ma1*Ra1*Ra2*Ca1+1j*omega*Ma1*(Ra1+Ra2))/\
        (Ra1+Ra2-(omega**2)*Ma1*Ra1*Ca1+1j*omega*(Ra1*Ra2*Ca1+Ma1))

    # Radiation impedance
    k0 = omega/c0;
    #s = pi*a^2;
    Jka = special.jv(1,2*k0*a)
    Ska = special.struve(1,2*k0*a)

    R1 = 1 - 2*Jka/(2*k0*a);
    X1 = 2*Ska/(2*k0*a);

    Zrad = (rho0*c0/(np.pi*a**2))*(R1 + 1j*X1)
    return Ra1, Ra2, Ca1, Ma1, Zrad_eq, Zrad

def pres_spkbaffle_odeg(freq, X, r, a, rho0 = 1.21, c0 = 343):
    w = 2 * np.pi * freq
    k0 = w/c0
    pres = 1j*2*rho0*c0 * X *\
        (np.sin((k0/2)*(np.sqrt(r**2+a**2)-r))) *\
        np.exp(-1j*(k0/2)*(np.sqrt(r**2+a**2)+r))
    return pres
```

# Imports

Vamos importar dados de medição e de simulação do SPICE. Os dados são
- Impedância elétrica medida e simulada
- Velocidade de vibração do cone (simulada)
- Curva de resposta medida a 1 [m] do falante no baffle infinito

***OBS: dados experimentais fornecidos pelo Eng. John William Skalee.***

```python
# Impedância medida
filename = '6w16p_Ze.txt'
data = np.loadtxt(filename, skiprows=0)
freq_m_Ze = data[:,0]
Ze_m = data[:,1]

# Impedância simulada
filename = 'Ze_Spice.txt'
data = np.loadtxt(filename, skiprows=1)
freq_s_Ze = data[:,0]
Ze_s = data[:,1] + 1j*data[:,2]

# Velocidade de vibração do cone (simulada)
filename = 'conevel_spice.txt'
data = np.loadtxt(filename, skiprows=1)
freq_s_vcone = data[:,0]
X_s = data[:,1] + 1j*data[:,2]

# Curva de resposta do alto-falante (1W/1m)
filename = '6w16p_p1W1m.txt'
data = np.loadtxt(filename, skiprows=0)
freq_m_p = data[:,0]
SPL_m = data[:,1]
```

# Dados de catálogo do alto-falante

```python
# lado elétrico
Vin = 5.66 # Amplitude da tensão de alimentação - (V_rms**2)/16 = 1 [W]
Re = 12.29 # Ohm
Le = 0.502e-3 # H

# lado mecânico
Mm = 15.7e-3 # kg
Cm = 0.113e-3 # m/N
Rm = 0.911 # Ns/m
Bl = 10.66 # Vs/m

# lado acústico
Sd = 143.4e-4 # m^2
ad = np.sqrt(Sd/np.pi) # considera a geometria cônica e parte da suspensão
Ra1, Ra2, Ca, Ma, Zrad_eq, Zrad = zrad_piston_infbaffle(ad, freq_s_vcone, rho0 = 1.21, c0 = 343)

print("1/Ra1: {}".format(1/Ra1))
print("1/Ra2: {}".format(1/Ra2))
print("Ca: {}".format(Ca))
print("Ma: {}".format(Ma))
```

    1/Ra1: 7.83344287107718e-05
    1/Ra2: 3.455171915283233e-05
    Ca: 1.2868020749841235e-08
    Ma: 4.835590271310726

```python
# Impedância elétrica (analítica)
w = 2 * np.pi * freq_s_vcone
Zem = (Bl**2)/(1j*w*Mm + Rm + (1/(1j*w*Cm))+(Sd**2)*Zrad)
Ze_a = 1j*w*Le + Re + Zem
```

```python
# Comparativo das impedâncias
fig = plt.figure(figsize = (7,5))
plt.semilogx(freq_m_Ze, Ze_m, '-', linewidth = 2, label = "Medido")
plt.semilogx(freq_s_Ze, np.abs(Ze_a), 'r',  linewidth = 2, label = "Analítico")
plt.semilogx(freq_s_Ze, np.abs(Ze_s), linewidth = 3, label = "LT-spice")
plt.legend()
plt.grid(linestyle = '--', which='both')
plt.xlim((20, 20000))
plt.xlabel('Frequência [Hz]')
plt.ylabel(r'$|Z_e|$ [$\Omega$]')
plt.tight_layout()
```

### Pressão sonora a 1 [m]

```python
# Comparativo entre as curvas de resposta
r = 1
# Pressão sonora calculada a partir da vibração do cone Spice
P_s = pres_spkbaffle_odeg(freq_s_vcone, X_s, r, ad, rho0 = 1.21, c0 = 343)
```

```python
# Pressão sonora calculada a partir da vibração do cone (modelo analítico)
X_a = (Bl/Ze_a)*(((Bl**2)/Ze_a) + 1j*w*Mm + Rm + (1/(1j*w*Cm)) + (Sd**2)*(Zrad))**(-1)
P_a = pres_spkbaffle_odeg(freq_s_vcone, X_a, r, ad, rho0 = 1.21, c0 = 343)
```

```python
fig = plt.figure(figsize = (7,5))
plt.semilogx(freq_m_p, SPL_m, '-', linewidth = 2, label = "Medido")
plt.semilogx(freq_s_vcone, 20*np.log10(np.abs(Vin*P_a/np.sqrt(2))/20e-6), 'r', linewidth = 2, label = "Analítico")
plt.semilogx(freq_s_vcone, 20*np.log10(np.abs(P_s/np.sqrt(2))/20e-6), linewidth = 2, label = "LT-spice")
plt.legend()
plt.grid(linestyle = '--', which='both')
plt.xlim((20, 20000))
plt.ylim((40, 120))
plt.xlabel('Frequência [Hz]')
plt.ylabel(r'$|\tilde{P}(r)|$ [dB]')
plt.tight_layout()
```


# Ransac and Outlier Removal

## Notebook Setup
The following cell will install Drake, checkout the manipulation repository, and set up the path (only if necessary).
- On Google's Colaboratory, this **will take approximately two minutes** on the first time it runs (to provision the machine), but should only need to reinstall once every 12 hours.

More details are available [here](http://manipulation.mit.edu/drake.html).

```python
import importlib
import os, sys
from urllib.request import urlretrieve

if 'google.colab' in sys.modules and importlib.util.find_spec('manipulation') is None:
    urlretrieve(f"http://manipulation.csail.mit.edu/scripts/setup/setup_manipulation_colab.py",
                "setup_manipulation_colab.py")
    from setup_manipulation_colab import setup_manipulation
    setup_manipulation(manipulation_sha='c1bdae733682f8a390f848bc6cb0dbbf9ea98602', drake_version='0.25.0', drake_build='releases')

# python libraries
import numpy as np

from manipulation import running_as_notebook

# Start a single meshcat server instance to use for the remainder of this notebook.
from meshcat.servers.zmqserver import start_zmq_server_as_subprocess
server_args = []
if 'google.colab' in sys.modules:
    server_args = ['--ngrok_http_tunnel']
proc, zmq_url, web_url = start_zmq_server_as_subprocess(server_args=server_args)

# TODO(russt): upstream this to drake
import meshcat.geometry as g
import meshcat.transformations as tf

from pydrake.all import RigidTransform, RotationMatrix, RollPitchYaw

import open3d as o3d
import meshcat

from IPython.display import clear_output
clear_output()

from manipulation import FindResource

# Visualize Stanford Bunny
pcd = o3d.io.read_point_cloud(FindResource("models/bunny/bun_zipper_res2.ply"))
pointcloud_model = np.asarray(pcd.points).transpose()

# First, clean the origin a bit to define nominal pose.
X = np.array([[1., 0., 0., 0.0],
              [0., np.cos(np.pi/2), -np.sin(np.pi/2), 0.],
              [0., np.sin(np.pi/2), np.cos(np.pi/2), -0.05]])
Xtemp = RigidTransform(X)
X = np.array([[np.cos(np.pi/2), -np.sin(np.pi/2), 0, 0.],
              [np.sin(np.pi/2), np.cos(np.pi/2), 0., 0.],
              [0., 0., 1., 0.]])
X = RigidTransform(X).multiply(Xtemp)

pointcloud_model = X.multiply(pointcloud_model)

# point clouds of planar surface
import numpy as np
grid_spec = 50
xy_axis = np.linspace(-0.5, 0.5, grid_spec)
plane_x, plane_y = np.meshgrid(xy_axis, xy_axis)
points_plane_xy = np.c_[plane_x.flatten(), plane_y.flatten(), np.zeros(grid_spec**2)]
bunny_w_plane = np.c_[points_plane_xy.T, pointcloud_model]

def fit_plane(xyzs):
    '''
    Args:
      xyzs is (N, 3) numpy array
    Returns:
      (4,) numpy array
    '''
    center = np.mean(xyzs, axis=0)
    cxyzs = xyzs - center
    U, S, V = np.linalg.svd(cxyzs)
    normal = V[-1]              # last row of V
    d = -center.dot(normal)
    plane_equation = np.hstack([normal, d])
    return plane_equation

# visualize a facet
def DrawFacet(vis, abcd, name, center=None,
              prefix='facets', radius=0.02, thickness=0.001, color=0xffffff, opacity=0.6):
    normal = np.array(abcd[:3]).astype(float)
    normal /= np.linalg.norm(normal)
    d = -abcd[3] / np.linalg.norm(normal)

    R = np.eye(3)
    R[:, 2] = normal
    z = normal
    if abs(z[0]) < 1e-8:
        x = np.array([0, -normal[2], normal[1]])
    else:
        x = np.array([-normal[1], normal[0], 0])
    x /= np.linalg.norm(x)
    R[:, 0] = x
    R[:, 1] = np.cross(z, x)

    X = np.eye(4)
    Rz = RollPitchYaw(np.pi/2, 0, 0).ToRotationMatrix().matrix()
    X[:3, :3] = R.dot(Rz)
    if center is None:
        X[:3, 3] = d * normal
    else:
        X[:3, 3] = center

    X_normal = X.copy()
    X_normal[:3, :3] = R

    material = meshcat.geometry.MeshLambertMaterial(
        color=color, opacity=opacity)

    vis[prefix][name]["plane"].set_object(
        meshcat.geometry.Cylinder(thickness, radius), material)

    normal_vertices = np.array([[0, 0, 0], [0, 0, radius]]).astype(float)
    vis[prefix][name]["normal"].set_object(
        meshcat.geometry.Line(meshcat.geometry.PointsGeometry(normal_vertices.T)))

    vis[prefix][name]["plane"].set_transform(X)
    vis[prefix][name]["normal"].set_transform(X_normal)

def generate_color_mat(color_vec, shape):
  color_mat = np.tile(np.array(color_vec).astype(np.float32).reshape(3,1), (1, shape[1]))
  return color_mat

vis = meshcat.Visualizer(zmq_url)
# vis = meshcat.Visualizer(zmq_url)
def visualize_point_clouds(pc_A, vis=None):
    if vis is None:
        vis = meshcat.Visualizer(zmq_url)
    vis["/Background"].set_property('visible', False)
    #vis["/Cameras/default/"].set_transform(tf.translation_matrix([0, 0, 1]))
    vis["/Cameras/default/rotated/<object>"].set_property("zoom", 10.5)

    vis["red_bunny"].set_object(g.PointCloud(pc_A, generate_color_mat([1, 0, 0], pc_A.shape), size=0.01))
    return vis
```

# Problem Description
In the lecture, we learned about the RANSAC algorithm. In this exercise, you will implement the RANSAC algorithm to separate the Stanford bunny from its environment!

**These are the main steps of the exercise:**
1. Implement the `ransac` method.
2. Implement the `remove_plane` method to remove the points that belong to the planar surface.

Let's first visualize the point clouds of Stanford bunny in meshcat!

```python
vis = visualize_point_clouds(bunny_w_plane, vis)
```

You should notice that now there is a planar surface underneath the bunny. You may assume the bunny is currently placed on a table, where the planar surface is the tabletop. In this exercise, your objective is to remove the planar surface.

A straightforward way to achieve a better fit is to remove the planar surface underneath the bunny. To do so, we provide you a function to fit a planar surface.

Recall that a plane equation is of the form
$$a x + b y + c z + d = 0$$
where $[a,b,c]^T$ is a vector normal to the plane and (if it's a unit normal) $d$ is the negative of the distance from the origin to the plane in the direction of the normal.  We'll represent a plane by the vector $[a,b,c,d]$.

The fitted planes are shown as translucent disks of radius $r$ centered at the points. The gray lines represent the planes' normals.

```python
plane_equation = fit_plane(bunny_w_plane.T)
print(plane_equation)
DrawFacet(vis, plane_equation, 'naive_plane', center=[0,0,-plane_equation[-1]], thickness=0.005, radius=0.1)
```

You should notice that the planar surface cannot be fitted exactly either. This is because it takes account of all points in the scene to fit the plane. Since a significant portion of the point cloud belongs to the bunny, the fitted plane is noticeably elevated above the ground.

To improve the result of the fitted plane, you will use RANSAC!

## RANSAC
With the presence of outliers (bunny), we can use RANSAC to get more reliable estimates. The idea is to fit a plane using many random choices of a minimal set of points (3), fit a plane for each one, count how many points are within a distance tolerance to that plane (the inliers), and return the estimate with the most inliers.

**Complete the function `ransac`.  It takes a data matrix, a tolerance, a value of iterations, and a model regressor. It returns an equation constructed by the model regressor and a count of inliers.**

```python
def ransac(point_cloud, model_fit_func, tolerance=1e-3, max_iterations=500):
    '''
    Args:
      point_cloud is (N, 3) numpy array
      tolerance is a float
      max_iterations is a (small) integer
    Returns:
      (4,) numpy array
    '''
    best_ic = 0                 # inlier count
    best_model = np.ones(4)     # plane equation ((4,) array)

    ##################
    # your code here
    ##################

    return  best_ic, best_model
```

Now you should have a lot better estimate of the planar surface with the use of RANSAC! Let's visualize the plane now!

```python
inlier_count, ransac_plane = ransac(bunny_w_plane.T, fit_plane, 0.001, 500)
print(ransac_plane)
DrawFacet(vis, ransac_plane, 'ransac_plane', center=[0,0,-ransac_plane[-1]], thickness=0.005, radius=0.1)
```

## Remove Planar Surface

Now all you need to do is to remove the points that belong to the planar surface. You may do so by rejecting all points that are
\begin{equation}
|| a x + b y + c z + d || > tol
\end{equation}

Note that since you are fitting a plane, the bunny is this case is the "outlier". Your job, however, is to keep the bunny and remove the planar surface.

**Complete the function below to remove the points that belongs to the planar surface**.

```python
def remove_plane(point_cloud, ransac, tol=1e-4):
    """
    Find the nearest (Euclidean) neighbor in point_cloud_B for each
    point in point_cloud_A.
    Args:
        point_cloud: Nx3 numpy array of points
        ransac: The RANSAC function to use (call ransac(args))
        plane_equation: (4,) numpy array, contains the coefficients of the plane
    Returns:
        point_cloud_wo_plane: Nx3 numpy array of points
    """
    point_cloud_wo_plane = np.zeros((100,3))
    return point_cloud_wo_plane
```

```python
bunny_wo_plane = remove_plane(bunny_w_plane.T, ransac)
vis = visualize_point_clouds(bunny_wo_plane.T, vis)
```

## How will this notebook be Graded?##

If you are enrolled in the class, this notebook will be graded using [Gradescope](www.gradescope.com). You should have gotten the enrollement code on our announcement in Piazza.

For submission of this assignment, you must do two things.
- Download and submit the notebook `ransac.ipynb` to Gradescope's notebook submission section, along with your notebook for the other problems.
- Copy and Paste your answer to the kinematic singularity problem to Gradescope's written submission section.

We will evaluate the local functions in the notebook to see if the function behaves as we have expected. For this exercise, the rubric is as follows:
- [4 pts] `ransac` must be implemented correctly.
- [2 pts] `remove_plane` must be implemented correctly.

```python
from manipulation.exercises.pose.test_ransac import TestRANSAC
from manipulation.exercises.grader import Grader

Grader.grade_output([TestRANSAC], [locals()], 'results.json')
Grader.print_test_results('results.json')
```


# Algoritmos de optimización - Seminario

Nombre y Apellidos:

Github: https://github.com/wisrovi/03MAIR---Algoritmos-de-Optimizacion---2019/blob/master/Seminario/WilliamSteveRodriguezVillamizar_Seminario-2.ipynb

Problema:

Colab: https://colab.research.google.com/github/wisrovi/03MAIR---Algoritmos-de-Optimizacion---2019/blob/master/Seminario/WilliamSteveRodriguezVillamizar_Seminario-2.ipynb
> 1.  Combinar cifras y operaciones

Descripción del problema:

Elección de grupos de población homogéneos

• El problema consiste en analizar el siguiente problema y diseñar un algoritmo que lo resuelva.

• Disponemos de las 9 cifras del 1 al 9 (excluimos el cero) y de los 4 signos básicos de las
operaciones fundamentales: suma(+), resta(-), multiplicación(*) y división(/)

• Debemos combinarlos alternativamente sin repetir ninguno de ellos para obtener una
cantidad dada.

....

(*) La respuesta es obligatoria

# Solución del problema usando algoritmos por recursividad y el algoritmo divide y venceras

(*)¿Cuantas posibilidades hay sin tener en cuenta las restricciones?

¿Cuantas posibilidades hay teniendo en cuenta todas las restricciones.

Respuesta

Debido a que el algoritmo genera en diversas ocasiones soluciones repetidas y no todas las combinaciones de numeros y signos son posibles, además de que cada valor ingresado tiene su própio número de soluciones único, siendo más facil generar soluciones para valores enteros que para valores flotantes.

No fue posible determinar una ecuación que entregara la cantidad de soluciones absoluta para todos los valores ingresados.

Por ello se ha elaborado un algoritmo para buscar toda la cantidad de respuestas posibles para un número dado, para determinar cuantas soluciones se pueden generar para la respuesta solicituda (se verá más adelante en el código).

En la evaluacion del algoritmo se ha logrado generar hasta 700 soluciones posibles para la respuesta de un entero de valor 10 (siendo este valor el que mayor soluciones se ha logrado encontrar.

Modelo para el espacio de soluciones

(*) ¿Cual es la estructura de datos que mejor se adapta al problema? Argumentalo.(Es posible que hayas elegido una al principio y veas la necesidad de cambiar, arguentalo)

Respuesta

Como este problema no tiene un dataseet inicial sino un dato de ingreso y con este generar una solución al problema,
sólo se puede decir que hay mejores soluciones para datos de entrada de tipo int menores a 100 que para datos float de cualquier valor

Esto se evidencia debido a que tratando de evaluar el mayor numero de soluciones para un dato, se ha logrado minimo 250 soluciones diferentes para datos int
mientras que para datos float sólo se han logrado 35 soluciones

Sin embargo si se pudiere usar una maquina con mayor GPU a la mia,
la cantidad de soluciones debería aumentar de 35 a 100 y de 250 a 1000 para float e int respectivamente.

Según el modelo para el espacio de soluciones

(*)¿Cual es la función objetivo?

(*)¿Es un problema de maximización o minimización?

Respuesta: Minimizacion

## Implementaciones Básicas de código para fuerza bruta y algoritmo final

```
import random
from sympy.parsing.sympy_parser import parse_expr
import numpy as np
```

```
Diccionario_signos = {
    "suma" : "+",
    "resta" : "-",
    "multiplicacion" : "*",
    "division" : "/"
    }

Lista_Numeros_Posibles = (1,
           2,
           3,
           4,
           5,
           6,
           7,
           8,
           9
          )
```

```
def BuscarExiste(lista, item):
    buscandoEncontrando = False
    for i in lista:
        if i == item:
            buscandoEncontrando = True
            break
    return buscandoEncontrando

def getNumeroAleatorio(listaNumeros = []):
    """
    usamos recursividad para crear este set de datos
    """
    if len(listaNumeros) < 5:
        numer = random.choice(Lista_Numeros_Posibles)
        if not BuscarExiste(listaNumeros, numer):
            listaNumeros.append(numer)
        getNumeroAleatorio(listaNumeros)
    return tuple(listaNumeros)

def getSignosAleatorios(listaSignos = []):
    """
    usamos recursividad para crear este set de datos
    """
    if len(listaSignos) < 4:
        sign = random.choice( list(Diccionario_signos.keys()) )
        if not BuscarExiste(listaSignos, sign):
            listaSignos.append(sign)
        getSignosAleatorios(listaSignos)
    return tuple(listaSignos)

def HallarResultado(tuplaNumeros, tuplaSignos):
    expresion = """%s %s %s %s %s %s %s %s %s""" %(
        str(tuplaNumeros[0]),
        Diccionario_signos[tuplaSignos[0]],
        str(tuplaNumeros[1]),
        Diccionario_signos[tuplaSignos[1]],
        str(tuplaNumeros[2]),
        Diccionario_signos[tuplaSignos[2]],
        str(tuplaNumeros[3]),
        Diccionario_signos[tuplaSignos[3]],
        str(tuplaNumeros[4])
    )
    solucion = float(parse_expr(expresion))
    solucion = "{0:.2f}".format(solucion)
    solucion = float(solucion)
    return (expresion, solucion)

def HallarValorDeseado(valorDeseado, stepsMax=10000):
    for i in range(stepsMax):
        (expresion, solucion) = HallarResultado(getNumeroAleatorio([]), getSignosAleatorios([]))
        if valorDeseado == solucion:
            return (expresion, "=", valorDeseado)

def BuscarOperacionesPorListado(valoresDeseados):
    respuestas = []
    numerosNoEncontrados = []
    for valorDeseado in valoresDeseados:
        rta = HallarValorDeseado(valorDeseado)
        if rta is None:
            numerosNoEncontrados.append(valorDeseado)
            #print("None = ", valorDeseado )
        else:
            respuestas.append(rta)
            #print(rta)
    return respuestas, numerosNoEncontrados
```

## Algoritmo Por fuerza bruta

Diseña un algoritmo para resolver el problema por fuerza bruta

Respuesta

```
#Buscando la cantidad máxima de soluciones posibles
valor = 5.25
cantidadSolucionesBuscar = 500 #Número probado de soluciones diferentes encontradas 500 para int y 150 float

valoresDeseados = []
for i in range(cantidadSolucionesBuscar):
    valoresDeseados.append(valor)

print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)

print("Numeros no encontrados: ")
for rta in numerosNoEncontrados:
    print(rta)
    pass

respuestas = sorted(set(respuestas))
print("Numeros con respuesta encontrada: ", len(respuestas) )
for rta in respuestas:
    #print(rta)
    pass
```

    Total numeros buscar respuesta:  500
    Numeros no encontrados:
    Numeros con respuesta encontrada:  337

```
print("solución a hallar: ", valor)
print()
print("solución mejor (usando los numero más pequeños posibles): " )
print(respuestas[0][0], respuestas[0][1], respuestas[0][2])
```

    solución a hallar:  5.25

    solución mejor (usando los numero más pequeños posibles):
    1 * 2 + 4 - 6 / 8 = 5.25

Calcula la complejidad del algoritmo por fuerza bruta

Respuesta

O(n log n)

## Algoritmo final (mejora al de fuerza bruta)

ok - (*)Diseña un algoritmo que mejore la complejidad del algortimo por fuerza bruta. Argumenta porque crees que mejora el algoritmo por fuerza bruta

Respuesta

```
valoresDeseados = [-3.5]

print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)

print("solución a hallar: ", valoresDeseados[0])
print()
print("solución mejor (usando los numero más pequeños posibles): " )
print(respuestas[0][0], respuestas[0][1], respuestas[0][2])
```

    Total numeros buscar respuesta:  1
    solución a hallar:  -3.5

    solución mejor (usando los numero más pequeños posibles):
    2 - 7 + 4 / 8 * 3 = -3.5

```
valoresDeseados = [-3.5, 2, 9, -9.5, 4, 1.3, 1,2,3,4,5,6,7,8,9]

print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)

print("Numeros no encontrados: ")
for rta in numerosNoEncontrados:
    print(rta)
    pass

print("Numeros con respuesta encontrada: ")
for rta in respuestas:
    print(rta)
    pass
```

    Total numeros buscar respuesta:  15
    Numeros no encontrados:
    1.3
    Numeros con respuesta encontrada:
    ('3 * 5 / 6 - 8 + 2', '=', -3.5)
    ('4 / 8 * 2 + 7 - 6', '=', 2)
    ('9 + 6 - 4 / 2 * 3', '=', 9)
    ('2 / 4 + 8 - 6 * 3', '=', -9.5)
    ('7 - 3 * 4 + 9 / 1', '=', 4)
    ('6 - 8 + 1 / 3 * 9', '=', 1)
    ('5 + 2 * 3 / 6 - 4', '=', 2)
    ('1 - 4 * 6 / 8 + 5', '=', 3)
    ('4 / 2 - 7 + 1 * 9', '=', 4)
    ('2 + 5 / 4 * 8 - 7', '=', 5)
    ('8 - 4 + 3 / 9 * 6', '=', 6)
    ('6 / 2 - 8 + 3 * 4', '=', 7)
    ('8 * 3 / 6 - 1 + 5', '=', 8)
    ('9 + 6 * 4 / 8 - 3', '=', 9)

```
valoresDeseados = range (-10, 11, 1)

print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)

print("Numeros no encontrados: ")
for rta in numerosNoEncontrados:
    print(rta)
    pass

respuestas = sorted(set(respuestas))
print("Numeros con respuesta encontrada: ")
for rta in respuestas:
    print(rta)
    pass
```

    Total numeros buscar respuesta:  21
    Numeros no encontrados:
    Numeros con respuesta encontrada:
    ('1 - 5 + 6 / 4 * 8', '=', 8)
    ('1 - 8 / 2 * 4 + 6', '=', -9)
    ('2 - 7 / 1 + 4 * 3', '=', 7)
    ('2 - 8 / 4 + 5 * 1', '=', 5)
    ('3 + 5 - 6 / 4 * 8', '=', -4)
    ('3 - 5 / 2 * 4 + 6', '=', -1)
    ('3 - 9 + 2 / 4 * 6', '=', -3)
    ('4 + 8 / 1 - 6 * 2', '=', 0)
    ('4 - 9 + 3 / 2 * 6', '=', 4)
    ('5 + 1 - 4 * 9 / 3', '=', -6)
    ('5 + 4 * 9 / 6 - 2', '=', 9)
    ('5 - 4 / 2 * 7 + 1', '=', -8)
    ('5 - 6 + 8 * 1 / 4', '=', 1)
    ('7 + 1 / 2 * 6 - 4', '=', 6)
    ('7 + 2 / 4 * 6 - 8', '=', 2)
    ('7 + 4 - 9 * 6 / 3', '=', -7)
    ('8 - 7 / 1 * 2 + 4', '=', -2)
    ('8 / 4 + 5 * 3 - 7', '=', 10)
    ('9 + 2 / 1 - 3 * 7', '=', -10)
    ('9 - 3 * 6 + 8 / 2', '=', -5)
    ('9 - 4 / 2 * 7 + 8', '=', 3)

```
valoresDeseados = []
valores = np.arange(-10, 11, 0.05)
for valorDeseado in valores:
    valorDeseado = float("{0:.2f}".format(valorDeseado))
    valoresDeseados.append(valorDeseado)

print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)
print("Con rta: ",len(respuestas))
print("Sin rta: ",len(numerosNoEncontrados))

print("Numeros no encontrados: ")
for rta in numerosNoEncontrados:
    print(rta)
    pass

print("Numeros con respuesta encontrada: ")
for rta in respuestas:
    print(rta)
    pass

```

    Total numeros buscar respuesta:  420
    Con rta:  162
    Sin rta:  258
    Numeros no encontrados:
    -9.95
    -9.9
    -9.85
    -9.7
    -9.65
    -9.6
    -9.55
    -9.45
    -9.35
    -9.3
    -9.15
    -9.1
    -9.05
    -8.95
    -8.9
    -8.85
    -8.7
    -8.65
    -8.6
    -8.55
    -8.45
    -8.35
    -8.3
    -8.15
    -8.1
    -8.05
    -7.95
    -7.9
    -7.85
    -7.7
    -7.65
    -7.6
    -7.55
    -7.45
    -7.35
    -7.3
    -7.15
    -7.1
    -7.05
    -6.95
    -6.9
    -6.85
    -6.7
    -6.65
    -6.6
    -6.55
    -6.45
    -6.35
    -6.3
    -6.15
    -6.1
    -6.05
    -5.95
    -5.9
    -5.85
    -5.7
    -5.65
    -5.55
    -5.45
    -5.35
    -5.3
    -5.25
    -5.2
    -5.15
    -5.1
    -5.05
    -4.95
    -4.9
    -4.85
    -4.7
    -4.65
    -4.55
    -4.45
    -4.35
    -4.3
    -4.15
    -4.1
    -4.05
    -3.95
    -3.9
    -3.85
    -3.7
    -3.65
    -3.55
    -3.45
    -3.35
    -3.3
    -3.15
    -3.1
    -3.05
    -2.95
    -2.9
    -2.85
    -2.7
    -2.65
    -2.55
    -2.45
    -2.35
    -2.3
    -2.15
    -2.1
    -2.05
    -1.95
    -1.9
    -1.85
    -1.7
    -1.65
    -1.55
    -1.45
    -1.35
    -1.3
    -1.15
    -1.1
    -1.05
    -0.95
    -0.9
    -0.85
    -0.7
    -0.65
    -0.55
    -0.45
    -0.35
    -0.3
    -0.15
    -0.1
    -0.05
    0.05
    0.1
    0.15
    0.3
    0.35
    0.45
    0.55
    0.65
    0.7
    0.85
    0.9
    0.95
    1.05
    1.1
    1.15
    1.3
    1.35
    1.45
    1.55
    1.65
    1.7
    1.85
    1.9
    1.95
    2.05
    2.1
    2.15
    2.3
    2.35
    2.45
    2.55
    2.65
    2.7
    2.85
    2.9
    2.95
    3.05
    3.1
    3.15
    3.3
    3.35
    3.45
    3.55
    3.65
    3.7
    3.85
    3.9
    3.95
    4.05
    4.1
    4.15
    4.3
    4.35
    4.45
    4.55
    4.65
    4.7
    4.85
    4.9
    4.95
    5.05
    5.1
    5.15
    5.3
    5.35
    5.45
    5.55
    5.65
    5.7
    5.85
    5.9
    5.95
    6.05
    6.1
    6.15
    6.3
    6.35
    6.45
    6.55
    6.65
    6.7
    6.85
    6.9
    6.95
    7.05
    7.1
    7.15
    7.3
    7.35
    7.45
    7.55
    7.65
    7.7
    7.85
    7.9
    7.95
    8.05
    8.1
    8.15
    8.3
    8.35
    8.45
    8.55
    8.65
    8.7
    8.85
    8.9
    8.95
    9.05
    9.1
    9.15
    9.3
    9.35
    9.45
    9.55
    9.65
    9.7
    9.85
    9.9
    9.95
    10.05
    10.1
    10.15
    10.3
    10.35
    10.45
    10.55
    10.65
    10.7
    10.85
    10.9
    10.95
    Numeros con respuesta encontrada:
    ('3 + 5 - 8 * 9 / 4', '=', -10.0)
    ('1 / 5 + 6 - 2 * 8', '=', -9.8)
    ('1 - 2 * 6 + 5 / 4', '=', -9.75)
    ('8 - 9 * 2 + 3 / 6', '=', -9.5)
    ('1 - 8 / 5 * 9 + 4', '=', -9.4)
    ('3 / 4 + 8 - 9 * 2', '=', -9.25)
    ('4 / 5 + 8 - 2 * 9', '=', -9.2)
    ('2 - 6 * 8 / 4 + 1', '=', -9.0)
    ('7 - 8 * 2 + 1 / 5', '=', -8.8)
    ('9 + 1 / 4 - 3 * 6', '=', -8.75)
    ('2 + 9 / 6 - 4 * 3', '=', -8.5)
    ('4 - 2 * 7 + 8 / 5', '=', -8.4)
    ('5 + 3 / 4 - 2 * 7', '=', -8.25)
    ('4 / 5 - 2 * 8 + 7', '=', -8.2)
    ('8 + 5 - 7 * 3 / 1', '=', -8.0)
    ('2 - 6 * 9 / 5 + 1', '=', -7.8)
    ('7 + 5 / 4 - 8 * 2', '=', -7.75)
    ('7 - 5 * 3 + 2 / 4', '=', -7.5)
    ('8 / 5 - 3 * 6 + 9', '=', -7.4)
    ('3 / 4 + 6 - 2 * 7', '=', -7.25)
    ('6 - 7 * 2 + 4 / 5', '=', -7.2)
    ('4 + 5 - 8 / 3 * 6', '=', -7.0)
    ('1 + 3 * 2 / 5 - 9', '=', -6.8)
    ('8 - 7 / 4 * 9 + 1', '=', -6.75)
    ('9 - 8 * 2 + 3 / 6', '=', -6.5)
    ('8 / 5 - 7 * 2 + 6', '=', -6.4)
    ('2 + 3 - 9 / 4 * 5', '=', -6.25)
    ('1 + 4 - 7 / 5 * 8', '=', -6.2)
    ('2 - 7 / 4 * 8 + 6', '=', -6.0)
    ('1 + 3 * 2 / 5 - 8', '=', -5.8)
    ('2 / 8 + 9 - 5 * 3', '=', -5.75)
    ('2 * 1 / 5 - 9 + 3', '=', -5.6)
    ('7 - 3 * 9 / 2 + 1', '=', -5.5)
    ('2 - 8 * 9 / 5 + 7', '=', -5.4)
    ('1 + 2 / 6 * 3 - 7', '=', -5.0)
    ('3 - 9 + 6 * 1 / 5', '=', -4.8)
    ('5 + 6 - 9 / 4 * 7', '=', -4.75)
    ('3 + 2 / 5 - 1 * 8', '=', -4.6)
    ('8 - 7 * 2 + 6 / 4', '=', -4.5)
    ('8 / 5 + 3 - 9 * 1', '=', -4.4)
    ('3 - 9 + 2 * 7 / 8', '=', -4.25)
    ('9 / 5 + 2 - 8 * 1', '=', -4.2)
    ('9 + 7 - 5 * 4 / 1', '=', -4.0)
    ('3 - 1 * 8 + 6 / 5', '=', -3.8)
    ('8 + 1 / 4 - 2 * 6', '=', -3.75)
    ('6 * 2 / 5 + 1 - 7', '=', -3.6)
    ('6 + 1 - 3 / 2 * 7', '=', -3.5)
    ('4 + 7 - 8 / 5 * 9', '=', -3.4)
    ('3 * 1 / 4 + 5 - 9', '=', -3.25)
    ('6 - 8 / 5 * 7 + 2', '=', -3.2)
    ('7 - 8 / 4 * 6 + 2', '=', -3.0)
    ('1 + 7 - 6 / 5 * 9', '=', -2.8)
    ('3 + 1 - 6 / 8 * 9', '=', -2.75)
    ('2 / 5 * 6 + 4 - 9', '=', -2.6)
    ('9 / 6 * 3 - 8 + 1', '=', -2.5)
    ('2 / 5 * 4 + 3 - 7', '=', -2.4)
    ('6 - 1 * 9 + 3 / 4', '=', -2.25)
    ('9 / 5 + 8 - 6 * 2', '=', -2.2)
    ('8 - 4 / 1 * 3 + 2', '=', -2.0)
    ('9 - 3 * 4 + 6 / 5', '=', -1.8)
    ('2 / 8 * 9 + 1 - 5', '=', -1.75)
    ('7 / 5 - 9 + 3 * 2', '=', -1.6)
    ('1 - 6 + 7 * 4 / 8', '=', -1.5)
    ('3 / 5 * 1 + 6 - 8', '=', -1.4)
    ('6 - 9 + 1 / 4 * 7', '=', -1.25)
    ('7 - 9 * 1 + 4 / 5', '=', -1.2)
    ('6 - 8 / 1 * 2 + 9', '=', -1.0)
    ('2 * 3 - 8 + 6 / 5', '=', -0.8)
    ('9 - 2 * 5 + 1 / 4', '=', -0.75)
    ('7 * 1 - 8 + 2 / 5', '=', -0.6)
    ('1 / 2 * 3 + 6 - 8', '=', -0.5)
    ('1 * 2 + 8 / 5 - 4', '=', -0.4)
    ('3 / 4 + 5 - 6 * 1', '=', -0.25)
    ('2 + 9 - 8 / 5 * 7', '=', -0.2)
    ('6 * 2 / 3 + 1 - 5', '=', 0.0)
    ('4 + 3 / 5 * 7 - 8', '=', 0.2)
    ('2 - 4 + 3 / 8 * 6', '=', 0.25)
    ('1 * 3 - 4 + 7 / 5', '=', 0.4)
    ('1 + 7 - 6 * 5 / 4', '=', 0.5)
    ('2 / 5 * 4 - 9 + 8', '=', 0.6)
    ('1 - 9 + 7 / 4 * 5', '=', 0.75)
    ('9 / 5 * 1 - 3 + 2', '=', 0.8)
    ('3 - 4 * 2 + 6 / 1', '=', 1.0)
    ('7 * 3 / 5 + 6 - 9', '=', 1.2)
    ('6 - 7 + 9 / 8 * 2', '=', 1.25)
    ('3 * 4 / 5 + 6 - 7', '=', 1.4)
    ('3 / 6 - 7 + 2 * 4', '=', 1.5)
    ('6 + 2 - 4 / 5 * 8', '=', 1.6)
    ('3 / 4 - 8 * 1 + 9', '=', 1.75)
    ('4 / 5 + 9 * 1 - 8', '=', 1.8)
    ('6 * 4 / 8 + 1 - 2', '=', 2.0)
    ('8 - 1 * 7 + 6 / 5', '=', 2.2)
    ('2 / 8 - 5 + 7 * 1', '=', 2.25)
    ('4 - 7 + 9 / 5 * 3', '=', 2.4)
    ('3 / 6 * 9 - 4 + 2', '=', 2.5)
    ('8 / 5 - 1 * 6 + 7', '=', 2.6)
    ('8 + 6 - 5 * 9 / 4', '=', 2.75)
    ('3 - 4 / 5 * 9 + 7', '=', 2.8)
    ('2 / 6 * 3 - 7 + 9', '=', 3.0)
    ('9 - 7 + 6 * 1 / 5', '=', 3.2)
    ('9 / 4 * 1 + 7 - 6', '=', 3.25)
    ('2 - 4 + 9 * 3 / 5', '=', 3.4)
    ('5 * 3 / 6 - 1 + 2', '=', 3.5)
    ('3 + 1 * 2 - 7 / 5', '=', 3.6)
    ('5 + 2 / 8 * 7 - 3', '=', 3.75)
    ('9 - 6 + 1 / 5 * 4', '=', 3.8)
    ('9 - 4 * 3 / 2 + 1', '=', 4.0)
    ('2 + 4 - 9 / 5 * 1', '=', 4.2)
    ('7 - 5 + 2 * 9 / 8', '=', 4.25)
    ('3 * 4 / 5 - 6 + 8', '=', 4.4)
    ('5 * 3 / 6 + 9 - 7', '=', 4.5)
    ('6 + 8 / 5 * 1 - 3', '=', 4.6)
    ('7 * 1 / 4 + 9 - 6', '=', 4.75)
    ('1 + 7 - 2 / 5 * 8', '=', 4.8)
    ('3 * 4 / 2 - 9 + 8', '=', 5.0)
    ('2 + 8 - 4 / 5 * 6', '=', 5.2)
    ('5 / 4 * 9 + 1 - 7', '=', 5.25)
    ('3 + 1 * 4 - 8 / 5', '=', 5.4)
    ('2 + 4 * 9 / 8 - 1', '=', 5.5)
    ('4 - 2 + 3 * 6 / 5', '=', 5.6)
    ('9 - 5 + 1 * 7 / 4', '=', 5.75)
    ('7 - 2 + 1 / 5 * 4', '=', 5.8)
    ('5 * 2 - 7 + 3 / 1', '=', 6.0)
    ('6 / 5 - 4 + 1 * 9', '=', 6.2)
    ('6 - 7 / 4 * 5 + 9', '=', 6.25)
    ('7 + 3 / 5 * 9 - 6', '=', 6.4)
    ('6 + 9 / 2 * 1 - 4', '=', 6.5)
    ('7 + 9 / 5 * 2 - 4', '=', 6.6)
    ('6 / 8 * 1 + 9 - 3', '=', 6.75)
    ('4 / 5 * 6 - 7 + 9', '=', 6.8)
    ('6 / 3 - 4 + 1 * 9', '=', 7.0)
    ('1 + 2 * 4 - 9 / 5', '=', 7.2)
    ('2 / 8 * 5 - 1 + 7', '=', 7.25)
    ('4 / 5 * 3 - 1 + 6', '=', 7.4)
    ('1 - 4 + 7 / 2 * 3', '=', 7.5)
    ('9 - 3 + 4 * 2 / 5', '=', 7.6)
    ('9 + 1 - 3 * 6 / 8', '=', 7.75)
    ('6 - 1 + 7 * 2 / 5', '=', 7.8)
    ('1 - 5 + 4 / 3 * 9', '=', 8.0)
    ('7 / 5 * 8 + 6 - 9', '=', 8.2)
    ('7 / 8 * 6 + 5 - 2', '=', 8.25)
    ('1 * 6 + 4 - 8 / 5', '=', 8.4)
    ('9 + 3 - 1 * 7 / 2', '=', 8.5)
    ('6 - 3 + 4 / 5 * 7', '=', 8.6)
    ('2 - 1 / 4 * 5 + 8', '=', 8.75)
    ('8 - 6 * 1 / 5 + 2', '=', 8.8)
    ('6 / 3 * 2 + 9 - 4', '=', 9.0)
    ('6 * 2 + 1 / 5 - 3', '=', 9.2)
    ('2 + 1 * 8 - 3 / 4', '=', 9.25)
    ('8 + 4 * 3 / 5 - 1', '=', 9.4)
    ('2 - 3 + 7 * 9 / 6', '=', 9.5)
    ('1 * 3 - 2 / 5 + 7', '=', 9.6)
    ('3 / 4 * 9 + 5 - 2', '=', 9.75)
    ('8 / 5 * 3 - 4 + 9', '=', 9.8)
    ('6 / 2 * 3 + 8 - 7', '=', 10.0)
    ('3 * 4 + 1 / 5 - 2', '=', 10.2)
    ('6 * 7 / 8 + 9 - 4', '=', 10.25)
    ('3 * 9 / 5 + 6 - 1', '=', 10.4)
    ('6 / 4 - 1 + 5 * 2', '=', 10.5)
    ('2 - 4 + 9 / 5 * 7', '=', 10.6)
    ('6 - 2 + 9 * 3 / 4', '=', 10.75)
    ('4 / 5 + 2 * 8 - 6', '=', 10.8)

Este algoritmo mejora al algoritmo de fuerza bruta debido a que al no tener que generar muchas soluciones para la misma entrada para luego elegir una, sino que se elije la qprimera que cumpla con lo solicitado, logrando así respuestas más rápidas.

(*)Calcula la complejidad del algoritmo

Respuesta

O(n^2)

Según el problema (y tenga sentido), diseña un juego de datos de entrada aleatorios

```
sizeDataseet = 20

valoresDeseados = []
for valorDeseado in range(sizeDataseet):
    valorAzar = random.random()*10
    valorAzar = float("{0:.2f}".format(valorAzar))
    valoresDeseados.append(valorAzar)

print(valoresDeseados)
```

    [2.3, 9.82, 2.15, 8.72, 3.18, 4.42, 4.31, 2.75, 4.76, 1.19, 0.51, 7.58, 9.02, 2.12, 0.22, 6.34, 1.52, 8.96, 8.13, 5.09]

Aplica el algoritmo al juego de datos generado

```
print("Total numeros buscar respuesta: ", len(valoresDeseados))
respuestas, numerosNoEncontrados  =BuscarOperacionesPorListado(valoresDeseados)
print("Con rta: ",len(respuestas))
print("Sin rta: ",len(numerosNoEncontrados))

print("Numeros no encontrados: ")
for rta in numerosNoEncontrados:
    print(rta)
    pass

print("Numeros con respuesta encontrada: ")
for rta in respuestas:
    print(rta)
    pass
```

    Total numeros buscar respuesta:  20
    Con rta:  3
    Sin rta:  17
    Numeros no encontrados:
    2.3
    9.82
    2.15
    8.72
    3.18
    4.42
    4.31
    4.76
    1.19
    0.51
    7.58
    9.02
    6.34
    1.52
    8.96
    8.13
    5.09
    Numeros con respuesta encontrada:
    ('2 / 8 * 3 + 9 - 7', '=', 2.75)
    ('4 - 3 + 9 * 1 / 8', '=', 2.12)
    ('1 - 3 + 5 / 9 * 4', '=', 0.22)

## Conclusiones

Para este problema se ha usado recursividad y el algoritmo de divide y venceras para la solución del problema, pero usando algoritmos heuristicos seguro se podrá lograr obtener los mismos resultados en un tiempo menor

Las restricciones del problema hacen que el algoritmo le resulte dificil generar soluciones para valores float no multiplos de 0.25....

El algoritmo generado fue por métodos exactos, pero un ejercicio futuro sería usar algoritmos heuristicos para la solución. Incluso la posibilidad de generar los set de signos y set de numeros (ambos son el set de datos) mediante algoritmos geneticos, y comparar las soluciones.