## Learning Process

Here in this section we are going to partly describy and in the remaining to test and evaluate performance of various machine learning models that we selected and adopted to built up learning models for accomplishing supervised machine learning tasks related to classification problems. More precisely, we focused on binary classification problemsa, since the target variables, that is T-OR-D feature, amongst the 12 features from which the dataset is made of, and from which the more or less hundered of records are described, is a binary categorical feature which can assume the two values represented by labels DECK and THROUGH describing in two distinct manner a property about each bridge within the dataset, that property refers to the system used for constructing the bridge surface, that is commonly called deck, for let veicheles or trains or whatevert to cross the rivers that are three distinct rivers: A, M, O. Where A stands for Allegheny river, while M stands for Monongahela river and lastly O stands for Ohio river.

Before describing the fine tuning process applied to the different models accordingly to their own major properties, characteristics and features, we have decided and established to test each model performance by looking at how well all of them is going just exploiting the default setting and runnng cross-validation protocol, in other word also referred to as policy, to check the accuracy level, as ana instance and some other metrics.

To be more detailed we follow the common machine leaning working flow, that requires to split the data set, after having preprocessed it properly and in a suitable manner to meet mahcine learning models needs, into subsets commonly referred  to as training set and test set. Where, the former is exploited to build up a inference model and the latter is used to check model's performance as weel as behavior up on held out sample of istances never seen before and so those examples that the learning model wasn't provided with to learn weights and select right hyper-params to plug in back into model at the end of training procedure, in order to later test the model with the found weights as well as hyper-parameters and if it meet our requirements in terms of performance values reached at test time, then it will be ready for deployments.

\begin{figure}[h]
\caption{Machine Learning Workflow Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow_2.png}
\end{figure}

As we can see from the image just above what we have to do, following the purposed machine learning scheme or work flow are the subsequent steps:

__(1) Split Dataset into Train and Test sets__:
After having done with preprocessing phase we separate or divide the dataset into training set and test set, where usually the training set is bigger than the test set, and in some cases  the test set, for recording some kind of performance measures, can be made of just a single instance against which the model will be tested and checked.

__(2) Split Train set into smaller Train set and a Validation set__:
Once  have made the first split then we hold out a part for later checking the test set and we focus on training set. In fact once we have selected a machine learning model amongst those we want to adopt to fit a model and compare the results obtained we try to indentify the best fit parameters to setting the model with them. To reach such a goal we can adopt different aproaches to split further the training set into a validation set and a smaller train set in order to emulate before doing test phase a possible beahvior of the trainng model once we think it is ready for the following phase that is test step.
There are several procedure for establishing how to divide training set into two new subsets, that are validation and a little bit smaller train set, where all of them are also connected or reffered to a learning algorithm called cross-validation which consists roughly speaking into testing the model against a smaller portion of training set in order to record and measure the performance of a model before saying we are ready to proceed with test phase. Among the existing Cross-Validation Procedure we adopt and so describe briefly the following:

- **K-fold Cross-Validation**: which is a cross validation protocol exploited so that we split the training set into K-folds, in other words K-subsets all of the same size, a part eventually the last one which will be the remainder set of samples. One at a time the K-fold are left out and the model is firstly trained against K-1 subsets(folds) and then test against the left out fold for recording the performanc. At the end of the k-times we have trained the model we have recorded the performance measures and we so can average the results and understand how model in average is well doing. In oder words we can either assume the mean value as the driving value to assume the model as satisfying our constraint on performance measures or adopt the best result amongst the k-trains as the settings for hyper-params to adopt. This procedure is feasible and suitable if we do not carea botu the fact that, in cases of classification tasks, the categories might be balanced or not in terms of number of instances, as well as, it can be adopted also if we want to show a learning curve and some other performance graphics or schemes as Confusion matrtices and ROC or recall-precision curves. Latly usual values for K are: 5, 10, 20.

\begin{figure}[h]
\caption{Cross Validation Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow/cross_validation.png}
\end{figure}

- **Leave One Out Cross-Validation**: It is a special case of the K-fold Cross-Validation. In fact, instead of adopting the usual values as 5, 10, 20, for K as the number of possible subsets, we establish to identify each single instance as a possible fold. It's clear that this algorithm requires more time to be completed and does not allow to show up the graphics cited just above, since we do not enough data for confusion matrix and ROC or recall-precision curves.

- **Stratified Cross-Validation**: It is a good compromise when the dataset is still large enough to be exploited for training purposes but we decide to split into K-folds the traning set such that each subset have the same proportion of samples coming from the differn classes. This operation reveals to be necessary or even mandatory when we detect that the dataset does not show the same number of samples, more or less, for each class, in other word when the dataset is unbalanced for a given attribute that is the target attribute. This means that, while trying to mitigate the issue about the unbalanced dataset we thunk as well as hope that this management let the model to fit a model which will not be affected heavily just from the most numerous class, bus still learn how to classify the samples coming from the other less numerous classes, without too mcuh misclassifying errors. As usual also with Stratified Cross-Validation we are able to show same graphics as with plain K-fold Cross-Validation, the difference is that the folds are not randomly sampled from the original training set, but yet are sampled in the same proportion per each class in order to have the same number of samples for each class inside each fold.

We try out all of the three described cross-validation techniques to measure how weel default settings for different models are doing to gaina a base line against wich compare later results coming from fine tuning process carried out exploiting grid search technique for selecting the best combinatin of purposed values for each candidate machine learning technique.

__(3) Grid Search approach__: is the technique adopted when taking into account one at a time all the machine learning algorithm for selecting the best set of hyper-parameters. It consists into defining for each model a grid of possible values for different hyper-parameters that in some sense represent our degree of freedom referred to some of the properties that characterize all the different models. The grid of values might be real number ranging within a more or less interval, or a string value used to trigger a certain feature of a model combined with other related aspects of the machine learning algorithm of the given model. We recall that the standard grid search will proceed until all possible combination of provided values have been test and training with such settings have been carried out. Opposite to classic grid search it is another technique called Random Grid Search, which implies instead to let a model to choose or sample randomly the hyper-parameters within the ranges or intervals related to each hyper-params. The latter technique can be potentially less expensive since we test a reduced number of combination but might be sub-optimal even if the results can be still acceptable and meaningful.

\begin{figure}[h]
\caption{Grid Search Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow/grid_search.png}
\end{figure}

__(4) Metrics and model performance's evaluation tools__: before deploying we have to test our model against a test set, that is a subset created or obtained from overall dataset which was, after preprocessing phase that turns feature values somehow into numbers, devided into two distinct sets generally of different size. The test set evaluation implies exploiting some metrics such as Accuracy, but yet there exist several others that partly are derived from confusion matrix evaluation tool, such as Precsio, Recal F1-score, and the like.

So what we understand is that we can make use of a bouch of metrics but rather than using directly those metrics we can explore model's performance by means of more useful tools such as Confusion Matrix, Roc curve in order to better understanding model's behavior when fed with unobserved new samples, as well as, how to set a threshold for determing when a target variable will suggest us that the classified sample belong to one class or the other. So, here we briefly describe which instruments we exploit to measure model's peformance starting from confusion matrix and moving ahead toward ROC curve.

**Confusion Matrix**: in statistics a confusion matrix it's a grid or matrix of numbers and in the simplest scenario, correspoding to a binary classification task, it aims at showing how well a model was going when applied onto unknow or preiviously unseen data points and  samples, in the following manner. Arbitrary we establish that along the rows we have the fraction of samples that the model has calssified or has assigned to agiven class, and so the rows account for *Predicted valuers*. Vice versa, along the columns we have the total number of samples for each class that all together resemble the so called *Actual Values* as we illustrate in the picture just below:

\begin{figure}[h]
\caption{Confusion Matrix Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow/confusion_matrix.png}
\end{figure}

So, such a table of numbers allows us to measure the fraction of correctly classified examples belonging to the Positive class, also referred to as *True Positive(TP)* or to the Negative class, also named *True Negative(TN)* and at the same time we can derive also the fractions of wrongly classified Positive samples and Negative ones, respectively knwon as *False Positve(FP)* and *False Negative (FN)*. It is clear that looking at the diagonal matrix we can undestand that the larger the value along the diagonal the better the model was able to correctly identify the samples accordingly to their own actual class.
From the four statistics measure depiceted above, that are TP,TN, FP, and FN, by the time have been dirved other useful metrics that can be exploited when the most used and well knwon measurement performance of accuracy is not enough due to the fact as an instance we wanto to analyze deeper our model behavior toward optimization of some goal or because we have to deal with dataset which are not balanced through classes to be predicted and so we wanto some other metrics to ensure the goodness of our solutions.

**Roc Curve**: for the reason cited some lines earlier by exploiting the four basis metrics have been developed other useful tools for ensuring the goodness of a solution, among those tools we decide to adopt the following one, knwon as Roc Curve. It is a curve, whose acronim stands for Receiver Operating Curve, largely employed in the field of *Decison Theory* and aims at finding, siggesting or showing how moedl's performance varywhen we are going to set different thresholds for a simple scenario in which we are going to solve a binary classification problem. Such a curve require to show on the x-axis the fraction of samples corresponding to *False Positve Rate (FPR)* at a different values of the model's hyper-params corresponding to threshold set for classifying items at inference time, as well as on the x-axis the fraction of samples corresponding to *True Positive Rate(TRP)* in order to plot a curve that originates at coordinates (0,0) and terminates at coordinates (1,1), varying in the middle accordingly to the pairs of values recorded at a given threshold for (FPR,TPR) pair. We are also reporting two driving curves that are respectvely the curve related to the *Random Classifier* which corresponds to a classifier that for each instance is just randomly selecting the predicted class, and the *Perfetct Classifier* that always correctly classifyes all the samples. Our goal by analysing such a graphics is to identify the threshold value such that we are near the points on the curve that are not so far from the upper-left corner so that to maximise the TPR as well as minimizing the FPR. Another useful quantity related to Roc Curve is represented by the so called amount Area Under the Curve (AUC) that suggests us how much area under the rco curve whas accounted while varying the threshold for classiffying the test samples, in particular the higher the value the better the classifier is doing varying the threshlds. Lastly we notice that the Random Sample accounts for an AUC equls to 0.5 %, while the Perfect Classifier for 1.0 % so we aim at obtaing a value for AUC that is in at least in the between but that approaches the unity. Here, below is presented an example of Roc Curve example:

\begin{figure}[h]
\caption{Roc Curve Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow/roc_curve.jpg}
\end{figure}

Lastly, before moving ahead with describing Machine Learning Models we provide a brief list of other useful metrics that can be exploited if necessary during our analyses:

\begin{figure}[h]
\caption{Roc Curve Example}
\centering
\includegraphics[width=0.5\textwidth]{images/machine_learning_workflow/summary_statistics.jpg}
\end{figure}

**Learning Curve**:
learning curves constitute a great tool to diagnose bias and variance in any supervised learning algorithm. It comes in handt when we have to face against the so called **Bias-Variance Trade-Off**. In order to explain what that trade-off implies we have to say briefly what follows:
- In supervised learning, we assume there's a real relationship between feature(s) and target and estimate this unknown relationship with a model. Provided the assumption is true, there really is a model, which we'll call $f$, which describes perfectly the relationship between features and target.
- In practice, $f$ is almost always completely unknown, and we try to estimate it with a model $\hat{f}$. We use a certain training set and get a certain $\hat{f}$. If we use a different training set, we are very likely to get a different $\hat{f}$. As we keep changing training sets, we get different outputs for $\hat{f}$. The amount by which $\hat{f}$ varies as we change training sets is called **variance**.
- While, for most real-life scenarios, however, the true relationship between features and target is complicated and far from linear. Simplifying assumptions give **bias** to a model. The more erroneous the assumptions with respect to the true relationship, the higher the bias, and vice-versa.
- In practice, however, we need to accept a trade-off. We can’t have both low bias and low variance, so we want to aim for something in the middle, knowing that:
\begin{equation}
\begin{cases}
Y = f(X) + \quad irreducible \quad error \\
f(X) = \hat{f(X)} + \quad reducible \quad error \\
Y = \hat{f(X)} + \quad reducible \quad error + \quad irreducible \quad error \\
 \end{cases}
\end{equation}

\begin{figure}[H]
	\centering
	\begin{subfigure}{.3\textwidth}
		\includegraphics[width=\textwidth]{images/machine_learning_workflow/biasvariance.png}
		\caption{Atom 1}
	\end{subfigure}
%%%%%%%%%%%%%%
	\begin{subfigure}{.3\textwidth}
		\includegraphics[width=\textwidth]{images/machine_learning_workflow/irr_error.png}
		\caption{Atom 2}
	\end{subfigure}
%%%%%%%%%%%%%%
\end{figure}

\begin{figure}[H]
	\centering
	\begin{subfigure}{.3\textwidth}
		\includegraphics[width=\textwidth]{images/machine_learning_workflow/add_data.png}
		\caption{Atom 1}
	\end{subfigure}
%%%%%%%%%%%%%%
	\begin{subfigure}{.3\textwidth}
		\includegraphics[width=\textwidth]{images/machine_learning_workflow/low_high_var.png}
		\caption{Atom 2}
	\end{subfigure}
%%%%%%%%%%%%%%
\end{figure}

**P-Value Analysis**: Here, in the following we are going to describe shortly what is and how we have exploited the statistical tool for assessing model performance from a particular point of view represented by the so called *P-value Analysis*. The p-value is widely used in *statistical hypothesis testing*, specifically in *null hypothesis significance testing*. In this method, as part of experimental design, before performing the experiment, one first chooses a model (the null hypothesis) and a threshold value for p, called the *significance level* of the test, traditionally *5% or 1%* and denoted as $\alpha$. If the p-value is less than the chosen significance level ($\alpha$), that suggests that the observed data is sufficiently inconsistent with the null hypothesis and that the null hypothesis may be rejected. However, that does not prove that the tested hypothesis is true. When the p-value is calculated correctly, this test guarantees that the type I error rate is at most $\alpha$. For typical analysis, using the standard $\alpha = 0.05$  cutoff, the null hypothesis is rejected when $\rho < .05$ and not rejected when $\rho > .05$. The p-value does not, in itself, support reasoning about the probabilities of hypotheses but is only a tool for deciding whether to reject the null hypothesis.

__(5)Deployment__: The last step for building a machine learning system comprise the deployment of one or more machine learning trained models that fit and satisfy constraints that was intially fixed before doing any kind of analysis. The main goal of deployemnt is to employ such statistics models for predicting, in other words making inference, against new data, unknown obeservatons for which we do not knwo their target class values.

### Pricipal Component Analysis

After having investigate the data points inside the dataset, I move one to another section of my report where I decide to explore examples that made up the entire dataset using a particular technique in the field of statistical analysis that corresponds, precisely, to so called Principal Component Analysis. In fact, the major objective of this section is understand whether it is possible to transform, by means of some kind of linear transformation given by a mathematical calculation, the original data examples into reprojected representation that allows me to retrieve most useful information to be later exploited at training time. So, lets dive a bit whitin what is and which are main concepts, pros and cons about Principal Component Analysis.

Firstly, we know that **Principal Component Analysis**, more shortly PCA, is a statistical procedure that uses an orthogonal transformation to convert a set of observations of possibly correlated variables into a set of values of linearly uncorrelated variables called *principal components*. This transformation is defined in such a way that:
- the first principal component has the largest possible variance (that is, accounts for as much of the variability in the data as possible),
- and each succeeding component in turn has the highest variance possible under the constraint that it is orthogonal to the preceding components.

The resulting vectors, each being a linear combination of the variables and containing n observations, are an uncorrelated orthogonal basis set. PCA is sensitive to the relative scaling of the original variables.

PCA is mostly used as a tool in *exploratory data analysis* and for making predictive models, for that reasons I used such a technique here, before going through the different learning technique for producing my models.

#### Several Different Implementation

From the theory and the filed of research in statistics, we know that out there, there are several different implementation and way of computing principal component analysis, and each adopted technique has different performance as well as numerical stability. The three major derivations are:
- PCA by means of an iterative based procedure of extraing pricipal components one after the other selecting each time the one that account for the most of variance along its own axis, within the remainig subspace to be derived.
- The second possible way of performing PCA is done via calculation of *Covariance Matrix* applied to attributes, that are our independent predictive variables, used to represent data points.
- Lastly, it is used the technique known as *Singular Valued Decomposition* applied to the overall data points within our dataset.

Reading scikit-learn documentation, I discovered that PCA's derivation uses the *LAPACK implementation* of the *full SVD* or a *randomized truncated SVD* by the method of *Halko et al. 2009*, depending on the shape of the input data and the number of components to extract. Therefore I will descrive mainly that way of deriving the method with respect to the others that, instead, will be described more briefly and roughly.

#### PCA's Iterative based Method
Going in order, as depicted briefly above, I start describing PCA obtained by means of iterative based procedure to extract one at a time a new principal componet explointing the data points at hand.

We begin, recalling that, PCA is defined as an orthogonal linear transformation that transforms the data to a new coordinate system such that the greatest variance by some scalar projection of the data comes to lie on the first coordinate (called the first principal component), the second greatest variance on the second coordinate, and so on.

We suppose to deal with a data matrix X, with column-wise zero empirical mean, where each of the n rows represents a different repetition of the experiment, and each of the p columns gives a particular kind of feature.

From a math poitn of view, the transformation is defined by a set of p-dimensional vectors of weights or coefficients $\mathbf {w} _{(k)}=(w_{1},\dots ,w_{p})_{(k)}$ that map each row vector $\mathbf{x}_{(i)}$ of X to a new vector of principal component scores ${\displaystyle \mathbf {t} _{(i)}=(t_{1},\dots ,t_{l})_{(i)}}$, given by: ${\displaystyle {t_{k}}_{(i)}=\mathbf {x} _{(i)}\cdot \mathbf {w} _{(k)}\qquad \mathrm {for} \qquad i=1,\dots ,n\qquad k=1,\dots ,l}$.

In this way all the individual variables ${\displaystyle t_{1},\dots ,t_{l}}$ of t considered over the data set successively inherit the maximum possible variance from X, with each coefficient vector w constrained to be a unit vector.

More precisely, the first component In order to maximize variance has to satisfy the following expression:

${\displaystyle \mathbf {w} _{(1)}={\underset {\Vert \mathbf {w} \Vert =1}{\operatorname {\arg \,max} }}\,\left\{\sum _{i}\left(t_{1}\right)_{(i)}^{2}\right\}={\underset {\Vert \mathbf {w} \Vert =1}{\operatorname {\arg \,max} }}\,\left\{\sum _{i}\left(\mathbf {x} _{(i)}\cdot \mathbf {w} \right)^{2}\right\}}$

So, with $w_{1}$ found, the first principal component of a data vector $x_{1}$ can then be given as a score $t_{1(i)} = x_{1} ⋅ w_{1}$ in the transformed co-ordinates, or as the corresponding vector in the original variables, $(x_{1} ⋅ w_{1})w_{1}$.

The others remainig components are computed as folloes. The kth component can be found by subtracting the first k − 1 principal components from X, as in the following expression:

- ${\displaystyle \mathbf {\hat {X}} _{k}=\mathbf {X} -\sum _{s=1}^{k-1}\mathbf {X} \mathbf {w} _{(s)}\mathbf {w} _{(s)}^{\rm {T}}}$

- and then finding the weight vector which extracts the maximum variance from this new data matrix  ${\mathbf {w}}_{{(k)}}={\underset {\Vert {\mathbf {w}}\Vert =1}{\operatorname {arg\,max}}}\left\{\Vert {\mathbf {{\hat {X}}}}_{{k}}{\mathbf {w}}\Vert ^{2}\right\}={\operatorname {\arg \,max}}\,\left\{{\tfrac {{\mathbf {w}}^{T}{\mathbf {{\hat {X}}}}_{{k}}^{T}{\mathbf {{\hat {X}}}}_{{k}}{\mathbf {w}}}{{\mathbf {w}}^{T}{\mathbf {w}}}}\right\}$

It turns out that:
- from the formulas depicted above me get the remaining eigenvectors of $X^{T}X$, with the maximum values for the quantity in brackets given by their corresponding eigenvalues. Thus the weight vectors are eigenvectors of $X^{T}X$.
- The kth principal component of a data vector $x_(i)$ can therefore be given as a score $t_{k(i)} = x_{(i)} ⋅ w_(k)$ in the transformed co-ordinates, or as the corresponding vector in the space of the original variables, $(x_{(i)} ⋅ w_{(k)}) w_{(k)}$, where $w_{(k)}$ is the kth eigenvector of $X^{T}X$.
- The full principal components decomposition of X can therefore be given as: ${\displaystyle \mathbf {T} =\mathbf {X} \mathbf {W}}$, where W is a p-by-p matrix of weights whose columns are the eigenvectors of $X^{T}X$.

#### Covariance Matrix for PCA analysis

PCA made from covarian matrix computation requires the calculation of sample covariance matrix of the dataset as follows: $\mathbf{Q} \propto \mathbf{X}^T \mathbf{X} = \mathbf{W} \mathbf{\Lambda} \mathbf{W}^T$.

The empirical covariance matrix between the principal components becomes ${\displaystyle \mathbf {W} ^{T}\mathbf {Q} \mathbf {W} \propto \mathbf {W} ^{T}\mathbf {W} \,\mathbf {\Lambda } \,\mathbf {W} ^{T}\mathbf {W} =\mathbf {\Lambda } }$.

#### Singular Value Decomposition for PCA analysis

Finally, the principal components transformation can also be associated with another matrix factorization, the singular value decomposition (SVD) of X, ${\displaystyle \mathbf {X} =\mathbf {U} \mathbf {\Sigma } \mathbf {W} ^{T}}$, where more precisely:
- Σ is an n-by-p rectangular diagonal matrix of positive numbers $σ_{(k)}$, called the singular values of X;
- instead U is an n-by-n matrix, the columns of which are orthogonal unit vectors of length n called the left singular vectors of X;
- Then, W is a p-by-p whose columns are orthogonal unit vectors of length p and called the right singular vectors of X.

factorizingn the matrix ${X^{T}X}$, it can be written  as:

${\begin{aligned}\mathbf {X} ^{T}\mathbf {X} &=\mathbf {W} \mathbf {\Sigma } ^{T}\mathbf {U} ^{T}\mathbf {U} \mathbf {\Sigma } \mathbf {W} ^{T}\\&=\mathbf {W} \mathbf {\Sigma } ^{T}\mathbf {\Sigma } \mathbf {W} ^{T}\\&=\mathbf {W} \mathbf {\hat {\Sigma }} ^{2}\mathbf {W} ^{T}\end{aligned}}$

Where we recall that ${\displaystyle \mathbf {\hat {\Sigma }} }$ is the square diagonal matrix with the singular values of X and the excess zeros chopped off that satisfies ${\displaystyle \mathbf {{\hat {\Sigma }}^{2}} =\mathbf {\Sigma } ^{T}\mathbf {\Sigma } } {\displaystyle \mathbf {{\hat {\Sigma }}^{2}} =\mathbf {\Sigma } ^{T}\mathbf {\Sigma } }$. Comparison with the eigenvector factorization of $X^{T}X$ establishes that the right singular vectors W of X are equivalent to the eigenvectors of $X^{T}X$ , while the singular values $σ_{(k)}$ of X are equal to the square-root of the eigenvalues $λ_{(k)}$ of $X^{T}X$ .

At this point we understand that using the singular value decomposition the score matrix T can be written as:

${\begin{aligned} \mathbf{T} & = \mathbf{X} \mathbf{W} \\ & = \mathbf{U}\mathbf{\Sigma}\mathbf{W}^T \mathbf{W} \\ & = \mathbf{U}\mathbf{\Sigma} \end{aligned}}$

so each column of T is given by one of the left singular vectors of X multiplied by the corresponding singular value. This form is also the polar decomposition of T.

Efficient algorithms exist to calculate the SVD, as in scikit-learn package, of X without having to form the matrix $X^{T}X$, so computing the SVD is now the standard way to calculate a principal components analysis from a data matrix

```python
from utils.all_imports import *;
%matplotlib inline
# Set seed for notebook repeatability
np.random.seed(0)
```

    None

```python
n = len('2,5,6,7,8,9,10'.split(','))
principal_components = [pc for pc in '2,5,6,7,8,9,10'.split(',')]
data = np.zeros(shape=(n, 7))

data[0:2,0:2] = [[0, 1], [1, 0]]
data[4:6,0+3] = [0, 1]

data
```

    array([[0., 1., 0., 0., 0., 0., 0.],
           [1., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0.]])

```python
# =========================================================================== #
# READ INPUT DATASET
# =========================================================================== #
dataset_path, dataset_name, column_names, TARGET_COL = get_dataset_location()
estimators_list, estimators_names = get_estimators()

dataset, feature_vs_values = load_brdiges_dataset(dataset_path, dataset_name)
columns_2_avoid = ['ERECTED', 'LENGTH', 'LOCATION']
# Array used for storing graphs
plots_names = list(map(lambda xi: f"{xi}_learning_curve.png", estimators_names))
pca_kernels_list = ['linear', 'poly', 'rbf', 'cosine', 'sigmoid']
cv_list = list(range(10, 1, -1))
```

```python
# Make distinction between Target Variable and Predictors
# --------------------------------------------------------------------------- #
rescaledX, y, columns = prepare_data_for_train(dataset, target_col=TARGET_COL)
```

    Summary about Target Variable {target_col}
    --------------------------------------------------
    2    57
    1    13
    Name: T-OR-D, dtype: int64
    shape features matrix X, after normalizing:  (70, 11)

```python
# sns.pairplot(dataset, hue=TARGET_COL, height=2.5);
```

```python
df = show_table_pc_analysis(X=rescaledX, show_cum_exp_kpca=True); df.to_csv('cumulative_varation_explained.csv')
df
```

    Cumulative varation explained(percentage) up to given number of pcs:
    Cumulative varation explained(percentage) up to given number of pcs:
    Cumulative varation explained(percentage) up to given number of pcs:
    Cumulative varation explained(percentage) up to given number of pcs:
    Cumulative varation explained(percentage) up to given number of pcs:
    Cumulative varation explained(percentage) up to given number of pcs:

      # PCS
      CumVarExp(%)
      Linear CumVarExp(%)
      Poly CumVarExp(%)
      Rbf CumVarExp(%)
      Sigmoid CumVarExp(%)
      Cosine CumVarExp(%)

      0
      2.0
      47.738342
      47.738342
      42.530458
      34.158927
      53.962444
      41.290635

      1
      5.0
      75.856460
      75.856460
      74.129318
      67.573023
      81.158209
      74.568239

      2
      6.0
      82.615768
      82.615768
      79.952749
      74.971362
      86.493958
      80.721521

      3
      7.0
      88.413903
      88.413903
      85.492450
      81.003755
      91.341605
      86.663670

      4
      8.0
      92.661938
      92.661938
      89.550432
      86.846031
      95.680993
      91.703753

      5
      9.0
      95.976841
      95.976841
      93.443925
      92.367208
      97.514014
      95.705986

      6
      10.0
      98.432807
      98.432807
      97.130597
      96.519913
      99.030672
      98.176194

#### Major Pros & Cons of PCA

### Test Error Rate versus Training Sample Size

Here, with the following set of graphics we aim at describing how the *Test Error Rate* changes across different *Training Set Size*, in  particular running the tests simply emploing the different estimators or classification algorithm with their default setting and running the training phase via cross-validation technique and adopint increasing training set size. Main goal with the illustration of such a bounch of graphics is to grasph a basic idea, that might be used to lead the training phase about how much the training set size affects the performance of a classifier at least with default settings of the hyper-parameter of the latter.

```python
try_comparing_various_online_solvers_by_kernel(X=rescaledX, y=y, kernels=None, heldout=[.75, .7, .65, .6, .55, .5, .45, .4, .35, .33, .3, .25, .2], rounds=20, n_classes=-1, n_components=9, estimators=estimators_list, cv=StratifiedKFold(2), verbose=0, show_fig=True, save_fig=False, stratified_flag=True, n_splits=3, random_state=42, gridshape=(3, 2), figsize=(15, 15), title="try comparing various online solvers by kernel", fig_name="try_comparing_various_online_solvers_by_kernel.png")
```

The main results we can get from the analysis we can carry out looking at the graphics just reported above is what follows:

- the *GuassianNB Classifier* in four out of five trials, that are more precisely trial for which we have employed kernel-tricks such as Linear, Polynomial, Rbf, Sigmoid the resulting estimators follow a trend is characterized by increasing and decrising in test error rate with some large picks after we have used at least half of the samples from the dataset. Furthermore, the Polynomial based Estimator results to be the worst among those cosidered earlier, in fact near the half of the training set size the trend even becomes increasing going apart from the decreasing trend characterized from alternating local maxima and minima. Finally, when observing how such a classifier behaves when trained adopting Cosine trick we can refer that it follows a trend somewhat similar to the one followed also from other classifier expect for decision trees classifier.

- the estimators based of *Decision Trees Classifier*, together with also *GuassianNB Classifiers*, are the two techniques amongst the others that were importantly affected by the training set size chosen for performing such analysis. In fact, even if in general is characterized from a decreasing trend, such a trend seems to be less stable than the others observed for the remaning classification techniques, in fact the trend is highly fluctuating with large picks when crossing and going towards larger training set sizes. The worst cases were when the classification technique was performed after having pre-preprocessed data examples by means of kernel-Pca based on Linear and Rbf tricks, because *Test Error Rate* turned to increasing after a given training set size which is larger than half of samples. While if we compare the generalization represented by Random Forest classifiers with respect to Decision Trees Classifier, it seems that the former across the different trials adopint also different kernel-trick for pre-processing the training examples follows a more stable decreasing trend for Test Error Rate versus training set size graphics, where just in two cases out of five which are trials where we have exploited Rbf and Sigmoid kernel-Pca tricks the random forest models seems to follwo in the second half of the Test Erro Rate curve a more fluctuating trend.

- while, speaking about all the other learning algorithms, what we have noticed is that in general the several training phase executed for the models varying the training set size follows a decreasing trend, which means either that allowing the estimatores to be trained by means of larger training set they gain more insights about how can be characterized training examples, in order to better discriminate between the two classes, or that models overfit to much against the training set. However, all the models seem to start at the very beginning with a Test Error Rate somewhat yet not too large and keep reducing it across the different training set size. More precisely, we have reported that Knn Classifier performs better across the different attempts when the training set size was in the range between half and *70%* of the training set size. While SVMs, RandomForests, and Sgd classifiers seem to reach better performance when training examples in the training set reaches let to use a traning set size in the range between *70% and 80%* of the training set size. Lastly the logisti regression classifier seems to follow a decreasing trend for Test Error Rate which results to be the most conservative, with a reduced number of fluctuation which also have been a contained changes or fluctuations.

```python
X = copy.deepcopy(rescaledX); n_components=9
learning_curves_by_kernels(estimators_list[:], estimators_names[:], X, y, train_sizes=np.linspace(.1, 1.0, 10), n_components=n_components, pca_kernels_list=pca_kernels_list[0], verbose=0, by_pairs=True, savefigs=True, figs_dest=os.path.join('figures', 'learning_curve', f"Pcs_{n_components}"))
```

What we can learn observig or looking at the bounch of graphics we have displayed just above is that not all of the different machine learning techiniques applied on dataset pre-processed by means of a precise kernel trick adopted for KernelPca show the same behavior, and even some plots suggest us that a machine learning algorithm may seem not suitable for such a small dataset for determining the well-known trend of a learning curve while exploiting a learning technique. To be more precise, we are going to speak roughly and briefly a little bit about each graphics, in the order as they have been shown here before, recalling that we have adopted up to 9 Principal components that account for nearly 95% of comulative explained variance related to the dataset at hand:

- The first general observation or thought that we can explain is that for all the pictures the initial gap between the two curves is most of the time wide, large, in other words, important, more or less about 20% in percentage.

- Speaking about both learning curve obtained from applying Gaussian Niave Bayes, before, and K-Nearest Neighbor, after, what we can say is that the curve related to Training Score is decreasing more or less of 10 point percentage, reaching 90% from the top, while the Cross-alidation curve seems to be constant for the first 6 trials and then to improve a little bit, but for the former model we can see increasing and descreasing that do not allow to fix a trend for measuring a gap between the two curves, instead for the latter model seems that the two curve converge at a certain point once the traingin set is more or less the overall dataset, which means we are going to exploit the most of information.

- Instead, if we focus on Logistic Regression technique and SGD Classifier, we have observed that before a given numebr of trials in both picitures the two curves, that are Trainign and Cross-validation score curves, seem to follow an independent trend increasing and descrising independently, but then the two curves start to follow a decreasing trend so we can hipotize that after that precise trials we may have reached the desired training size.

- The learning curve associated with SVC classifier suggests us that it seems to behave looking at both Traingin and Cross-Validation curve more or less as the previous graphics, but while the Training curve as decreases loosing accuracy score value such a curve tightenes the varaince, instead the Cross-validation curve keeps a wide varaince stretching it while increasing the training set size, meaaning that the models seem to be more unsure about the results.

- The last two graphics are the most problematics and questionable ones, since while enalarging the training size we do not see either improvements or worsening referring to the Training curve, while the Cross-lvalidation curve seems at the very beginning to show a samew behavior as the Training curve, but then after a sequence of trials with increasing training size we get an improvement in terms of accuracy scores. But, since we normally expect to observe a learning curve where Training curve as well as Cross-validation curve respectively decreases and increases their performance at least at a given point where we hope the gap existing between them will be as small as possible wecan think that such those two graphics are nto explaining directly the real issues we face while dealing with Decsion Tree and Radom Forest Classifiers with such a small dataset.

### References
- Metrics and Diagnositc Tools:
    - (__Confusion Matrix__) https://en.wikipedia.org/wiki/Confusion_matrix
    - (__F-1-Sccore, Accuracy, and Precision-Recall__) https://towardsdatascience.com/beyond-accuracy-precision-and-recall-3da06bea9f6c
    - (__Roc Curve__) https://en.wikipedia.org/wiki/Receiver_operating_characteristic
    - (__P-value__) https://en.wikipedia.org/wiki/P-value
- Statistics:
    - (__Correlation and dependence__) https://en.wikipedia.org/wiki/Correlation_and_dependence

pittsburgh-bridges-data-set-analysis/models-analyses/merge_analyses/latex_merge/Section 1.2 - Learning Process and Pca Analysis-backup.ipynb


Modelos Matemáticos de Ingeniería

Sesión 06 - Diferencias finitas

2021/02

MEDELLÍN - COLOMBIA

 Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license.(c) Carlos Alberto Alvarez Henao

***

***Docente:*** Carlos Alberto Álvarez Henao, I.C. D.Sc.

***e-mail:*** carlosalvarezh@gmail.com

***skype:*** carlos.alberto.alvarez.henao

***Linkedin:*** https://www.linkedin.com/in/carlosalvarez5/

***github:*** https://github.com/carlosalvarezh/Dinamica

***Herramienta:*** [Jupyter](http://jupyter.org/)

***Kernel:*** Python 3.9

***

Tabla de Contenidos

1  Diferencias Finitas

1.1  Series de Taylor

1.2  Esquemas para la primera derivada en 1D

1.2.1  Primer orden hacia adelante (forward)

1.2.2  Primer orden hacia atrás (backward)

1.2.3  Segundo orden (central)

1.2.4  Resumen esquemas para la primera derivada

1.3  Esquemas para la segunda derivada en 1D

1.4  Ejemplos de implmentación computacional

1.5  Esquemas en 2D

1.6  Término difusivo no uniforme en 1D

2  Molécula computacional de una aproximación en Diferencias Finitas

2.1  Ejemplo Ecuación de Laplace

3  Discretización de ecuaciones por el método de Diferencias Finitas

3.1  Estado estacionario

3.2  Ejemplo: ecuación de transporte en estado estacionario en 1D

3.3  Ecuaciones No estacionarias

3.3.1  Introducción

3.3.2  Discretización de ecuaciones no estacionarias

3.3.3  Discretización explícita

3.3.4  Discretización implícita

3.3.5  Resumen

3.4  Discretización multidimensional

4  Consistencia, Convergencia y Estabilidad

4.1  Introducción

4.2  Consistencia

4.3  Convergencia

4.4  Estabilidad

4.4.1  Teorema de Lax

4.4.2  Condición CFL

4.4.3  Relación Consistencia - Convergencia - Estabilidad

4.5  Esquema Upwind

4.6  Solución de la ecuación de convección-difusión en estado estacionario en 1D empleando el esquema Upwind

4.7  Esquema de Viscosidad artificial

4.8  Ecuación de convección difusión dependiente del tiempo

4.9  Solución implícita

## Diferencias Finitas

En [análisis numérico](https://en.wikipedia.org/wiki/Numerical_analysis), los métodos de diferencias finitas (FDM) son una clase de técnicas numéricas para resolver ecuaciones diferenciales mediante la aproximación de derivadas con diferencias finitas. Tanto el dominio espacial como el intervalo de tiempo (si corresponde) se discretizan o se dividen en un número finito de pasos, y el valor de la solución en estos puntos discretos se aproxima resolviendo ecuaciones algebraicas que contienen diferencias finitas y valores de puntos cercanos.

Los métodos de diferencias finitas convierten las ecuaciones diferenciales ordinarias (EDO) o las ecuaciones diferenciales parciales (PDE), que pueden ser no lineales, en un sistema de ecuaciones lineales que pueden resolverse mediante técnicas de álgebra matricial. Las computadoras modernas pueden realizar estos cálculos de álgebra lineal de manera eficiente, lo que, junto con su relativa facilidad de implementación, ha llevado al uso generalizado de FDM en el análisis numérico moderno. Hoy en día, FDM es uno de los enfoques más comunes para la solución numérica de PDE, junto con los métodos de elementos finitos.

### Series de Taylor

La [serie de Taylor](https://en.wikipedia.org/wiki/Taylor_series) de una función es una suma infinita de términos que se expresan en términos de las derivadas de la función en un solo punto. Para las funciones más comunes, la función y la suma de su serie de Taylor son iguales cerca de este punto. Las series de Taylor llevan el nombre de [Brook Taylor](https://en.wikipedia.org/wiki/Brook_Taylor), quien las presentó en 1715.

La suma parcial formada por los primeros $n + 1$ términos de una serie de Taylor es un polinomio de grado $n$ que se llama el *$n$-ésimo polinomio de Taylor de la función*. Los polinomios de Taylor son aproximaciones de una función, que generalmente se vuelven mejores a medida que $n$ aumenta. El [teorema de Taylor](https://en.wikipedia.org/wiki/Taylor%27s_theorem) da estimaciones cuantitativas sobre el error introducido por el uso de tales aproximaciones. Si la serie de Taylor de una función es convergente, su suma es el límite de la secuencia infinita de los polinomios de Taylor.

### Esquemas para la primera derivada en 1D

De la [serie de Taylor](https://en.wikipedia.org/wiki/Taylor_series)

\begin{equation*}
f(x_{i \pm 1}) = f(x_i) \pm f'(x_i)h + \frac{f''(x_i)h^2}{2!} \pm \frac{f'''(x_i)h^3}{3!} + \ldots
\label{eq:Ec6_1} \tag{6.1}
\end{equation*}

con $h=\Delta x = x_{i+1}-x_i$ siendo el tamaño de paso.

Dada que la serie contiene infinitos términos, partir de la ecuación [(6.1)](#Ec6_1) se pueden obtener infinitos esquemas numéricos para determinar cada una de las infinitas derivadas de dicho polinomio.

#### Primer orden hacia adelante (forward)

De la ecuación [(6.1)](#Ec6_1) tomando los valores positivos, que involucran únicamente términos hacia adelante, se trunca la serie hasta la primera derivada y se realiza un despeje algebraico para llegar a:

\begin{equation*}
f'(x_i) = \frac{f(x_{i+1})-f(x_i)}{h} + \mathcal{O}(h)
\label{eq:Ec6_2} \tag{6.2}
\end{equation*}

se puede observar que el término $\mathcal{O}(h)$ indica que el error es de orden lineal, es decir, si se reduce el tamaño de paso, $h$, a la mitad, el error se reducirá a la mitad. Si se reduc el tamaño de paso a una cuarta parte, el error se reducirá, linealmente, una cuarta parte.

#### Primer orden hacia atrás (backward)

De la ecuación [(6.1)](#Ec6_1) tomando los valores negativos, que involucran únicamente términos hacia atrás (backward). Se trunca la serie hasta la primera derivada y se realiza un despeje algebraico para llegar a:

\begin{equation*}
f'(x_i) = \frac{f(x_{i})-f(x_{i-1})}{h} + \mathcal{O}(h)
\label{eq:Ec6_3} \tag{6.3}
\end{equation*}

se observa que se llega a una expresión similar a la de la ecuación [(6.2)](#Ec6_2), pero de esta vez, se tiene en cuenta es el valor anterior al punto $x_i$. También se observa que el error es de orden lineal, por lo que se mantiene un esquema de primer orden.

#### Segundo orden (central)

Una forma de aumentar el orden de estos esquemas, es realizar el truncamiento de la *serie de Taylor* hasta la segunda derivada, hacia adelante y hacia atras, y realizar su resta aritmética.

\begin{equation*}
\begin{split}
f(x_{i+1}) & = f(x_i) + f'(x_i)h + \frac{f''(x_i)h^2}{2!} \\
& - \\
f(x_{i-1}) & = f(x_i) - f'(x_i)h + \frac{f''(x_i)h^2}{2!} \\
\hline \\
f(x_{i+1}) - f(x_{i-1}) & = 2 f'(x_i)h
\end{split}
\label{eq:Ec6_4} \tag{6.4}
\end{equation*}

de la anterior ecuación, despejando el término que corresponde a la primera derivada queda:

\begin{equation*}
\begin{split}
f'(x_i) = \frac{f(x_{i+1}) - f(x_{i-1})}{2h} + \mathcal{O}(h^2)
\end{split}
\label{eq:Ec6_5} \tag{6.5}
\end{equation*}

se llega al esquema de diferencias finitas central para la primera derivada, que es de orden dos, es decir, si se disminuye el tamaño de paso, $h$, a la mitad, el error se disminuye una cuarta partes. En principio, esta es una mejor aproximación que los dos esquemas anteriores. La selección del esquema dependerá de la disponibilidad de puntos y del fenómeno físico a tratar.

#### Resumen esquemas para la primera derivada

Como la serie de Taylor es infinita, se podrían determinar infinitos esquemas de diferentes ordenes para la primera derivada. En la siguiente tabla se presentan algunos esquemas de diferencias finitas para la primera derivada de diferentes órdenes. Se deja al estudiante la consulta de otros esquemas.

|***Esquema***|***Función***|***Error***|
|:-----:|:-----:|:---:|
|***Forward***|$$f´(x_0)=\frac{f(x_0+h)-f(x_0)}{h}$$|$$\mathcal{O}(h)$$|
|       |$$f´(x_0)=\frac{-3f(x_0)+4f(x_0+h)-f(x_0+2h)}{2h}$$|$$\mathcal{O}(h^2)$$|
|***Central***|$$f´(x_0)=\frac{f(x_0+h)-f(x_0-h)}{2h}$$|$$\mathcal{O}(h^2)$$|
|       |$$f´(x_0)=\frac{f(x_0-2h)-8f(x_0-h)+8f(x_0+h)-f(x_0+2h)}{12h}$$|$$\mathcal{O}(h^4)$$|
|***Backward***|$$f´(x_0)=\frac{f(x_0)-f(x_0-h)}{h}$$|$$\mathcal{O}(h)$$|
|        |$$f´(x_0)=\frac{f(x_0-2h)-4f(x_0-h)+3f(x_0)}{2h}$$|$$\mathcal{O}(h^2)$$|

### Esquemas para la segunda derivada en 1D

Siguiendo con la misma forma de abordar el problema para la primera derivada, si se amplian los términos en la serie de Taylor hasta la tercera derivada tanto hacia adelante como hacia atrás, y se suman, se llega a:

\begin{equation*}
\begin{split}
f(x_{i+1}) & = f(x_i) + f'(x_i)h + \frac{f''(x_i)h^2}{2!} + \frac{f'''(x_i)h^3}{3!}\\
& + \\
f(x_{i-1}) & = f(x_i) - f'(x_i)h + \frac{f''(x_i)h^2}{2!} - \frac{f'''(x_i)h^3}{3!}\\
\hline \\
f(x_{i+1}) + f(x_{i-1}) & = 2 f(x_i) + 2f''(x_i)\frac{h^2}{2!} + \mathcal{O}(h^3)
\end{split}
\label{eq:Ec6_6} \tag{6.6}
\end{equation*}

Despejando para el término de la segunda derivada, se llega a:

\begin{equation*}
\begin{split}
f''(x_i) = \frac{f(x_{i+1}) - 2f(x_i) + f(x_{i-1})}{h^2} + \mathcal{O}(h^3)
\end{split}
\label{eq:Ec6_7} \tag{6.7}
\end{equation*}

Que corresponde a un esquema de diferencias finitas de segundo orden para la segunda derivada. A este esquema también se le llama "*molécula de tres puntos*"

Igual que para la primera derivada, se pueden determinar infinitos esquemas de diferentes órdenes para la segunda derivada, y derivadas superiores. A continuación se muestra un cuadro resumen de algunos esquemas de diferencias finitas para la segunda derivada. Se deja al estudiante la revisión de esquemas de mayor orden para la segunda derivada y derivadas superiores.

|***Esquema***|***Función***|***Error***|
|:-----:|:-----:|:---:|
|***Forward***|$$f''(x_0)=\frac{f(x_0)-2f(x_0+h)+f(x_0+2h)}{h^2}$$|$$\mathcal{O}(h)$$|
|       |$$f''(x_0)=\frac{2f(x_0)-5f(x_0+h)+4f(x_0+2h)-f(x_0+3h)}{h^2}$$|$$\mathcal{O}(h^2)$$|
|***Central***|$$f''(x_0)=\frac{f(x_0-h)-2f(x_0)+f(x_0+h)}{h^2}$$|$$\mathcal{O}(h^2)$$|
|       |$$f''(x_0)=\frac{-f(x_0-2h)+16f(x_0-h)-30f(x_0)+16f(x_0+h)-f(x_0+2h)}{12h^2}$$|$$\mathcal{O}(h^4)$$|
|***Backward***|$$f''(x_0)=\frac{f(x_0-2h)-2f(x_0-h)+f(x_0)}{h}$$|$$\mathcal{O}(h^2)$$|
|        |$$f''(x_0)=\frac{-f(x_0-3h)+4f(x_0-2h)-5f(x_0-h)+2f(x_0)}{h^2}$$|$$\mathcal{O}(h^2)$$|

### Ejemplos de implmentación computacional

```python
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym

sym.init_printing(use_latex='mathjax')
```

```python
def f(x):
    return x**2 - x + 1
```

```python
def d1forward(x0, h):
    return (f(x0 + h) - f(x0)) / h
```

```python
def d1backward(x0, h):
    return (f(x0) - f(x0 - h)) / h
```

```python
def d1central(x0, h):
    return (f(x0 + h) - f(x0 - h) ) / (2 * h)
```

```python
x = 3.5
h = 0.5
```

```python
d1f = d1forward(x, h)
print("El valor de la primera derivada en {0:4.2f} usando forward con un tamaño de paso {2:6.5f} es: {1:6.5f}".format(x,d1f,h))
d1b = d1backward(x, h)
print("El valor de la primera derivada en {0:4.2f} usando backward con un tamaño de paso {2:6.5f} es: {1:6.5f}".format(x,d1b,h))
d1c = d1central(x, h)
print("El valor de la primera derivada en {0:4.2f} usando central con un tamaño de paso {2:6.5f} es: {1:6.5f}".format(x,d1c,h))
```

    El valor de la primera derivada en 3.50 usando forward con un tamaño de paso 0.50000 es: 6.50000
    El valor de la primera derivada en 3.50 usando backward con un tamaño de paso 0.50000 es: 5.50000
    El valor de la primera derivada en 3.50 usando central con un tamaño de paso 0.50000 es: 6.00000

### Esquemas en 2D

De la serie truncada de Taylor en 2D, sea $\phi=\phi(x,y)$

\begin{equation*}
\begin{split}
\phi(x_{i+1},y_{i+1})=\phi(x_i,y_i)+ \left.\Delta x \frac{\partial \phi}{\partial x}\right|_{i,j}+ \left.\Delta y \frac{\partial \phi}{\partial y}\right|_{i,j} + \left.\frac{(\Delta x)^2}{2!}\frac{\partial^2\phi}{\partial x^2}\right|_{i,j} + \left. 2\frac{(\Delta x)(\Delta y)}{2!}\frac{\partial^2 \phi}{\partial x \partial y} \right|_{i,j}+ \left.\frac{(\Delta y)^2}{2!}\frac{\partial^2\phi}{\partial y^2}\right|_{i,j} + R_N
\end{split}
\label{eq:Ec6_8} \tag{6.8}
\end{equation*}

De la anterior ecuación, el único término que faltaría por determinar es el correspondiente a la derivada mixta en $x$ y $y$, que está dada por:

\begin{equation*}
\begin{split}
\frac{\partial^2 \phi}{\partial x \partial y} = \frac{\phi_{i+1,j+1}-\phi_{i+1,j-1}-\phi_{i-1,j+1}+\phi_{i-1,j-1}}{4(\Delta x)(\Delta y)} + O \left[ (\Delta x)^2,(\Delta y)^2 \right ]
\end{split}
\label{eq:Ec6_9} \tag{6.9}
\end{equation*}

### Término difusivo no uniforme en 1D

\begin{equation*}
\begin{split}
\frac{\partial}{\partial x}\left( \Gamma \frac{\partial \phi}{\partial x} \right)
\end{split}
\label{eq:Ec6_10} \tag{6.10}
\end{equation*}

donde $\Gamma$ es el coeficiente de difusión o viscosidad

\begin{equation*}
\begin{split}
\left. \frac{\partial}{\partial x}\left( \Gamma \frac{\partial \phi}{\partial x} \right) \right |_{i}=\frac{\left(\Gamma \frac{\partial \phi}{\partial x}\right)_{i+1/2}-\left(\Gamma \frac{\partial \phi}{\partial x}\right)_{i-1/2}}{\Delta x}+O(\Delta x)^2
\end{split}
\label{eq:Ec6_11} \tag{6.11}
\end{equation*}

Se realiza entonces una aproximación en los puntos $x_{i \pm 1/2}$

\begin{equation*}
\begin{split}
\left. \frac{\partial}{\partial x}\left( \Gamma \frac{\partial \phi}{\partial x} \right) \right |_{i} = \frac{\Gamma_{i+1/2}\frac{\phi_{i+1}-\phi_i}{\Delta x}-\Gamma_{i-1/2}\frac{\phi_{i}-\phi_{i-1}}{\Delta x}}{\Delta x}+O(\Delta x)^2
\end{split}
\label{eq:Ec6_12} \tag{6.12}
\end{equation*}

simplificando,

\begin{equation*}
\begin{split}
\left. \frac{\partial}{\partial x}\left( \Gamma \frac{\partial \phi}{\partial x} \right) \right |_{i} = \Gamma_{i+1/2} \frac{\phi_{i+1}-\phi_i}{(\Delta x)^2}-\Gamma_{i-1/2}\frac{\phi_{i}-\phi_{i-1}}{(\Delta x)^2}+O(\Delta x)^2
\end{split}
\label{eq:Ec6_13} \tag{6.13}
\end{equation*}

Ahora, promediando los valores de $\Gamma_{i \pm1/2}$,

\begin{equation*}
\begin{split}
\Gamma_{i+1/2}=\frac{\Gamma_i+\Gamma_{i+1}}{2} \quad\quad \Gamma_{i-1/2}=\frac{\Gamma_i+\Gamma_{i-1}}{2}
\end{split}
\label{eq:Ec6_14} \tag{6.14}
\end{equation*}

## Molécula computacional de una aproximación en Diferencias Finitas

### Ejemplo Ecuación de Laplace

En cálculo vectorial, la [ecuación de Laplace](https://en.wikipedia.org/wiki/Laplace%27s_equation) es una ecuación en derivadas parciales de segundo orden de tipo elíptico, que recibe ese nombre en honor al físico y matemático Pierre-Simon Laplace. La ecuació de Laplace en dos dimensiones se expresa como:

\begin{equation*}
\begin{split}
\nabla^2 \phi & = 0 \\
& = \left. \frac{\partial^2 \phi}{\partial x^2} \right|_{i,j} + \left. \frac{\partial^2 \phi}{\partial y^2} \right|_{i,j}
\end{split}
\label{eq:Ec6_15} \tag{6.15}
\end{equation*}

Empleando un esquema de diferencias finitas central para la segunda derivada, se tiene la discretización espacial como:

\begin{equation*}
\begin{split}
\nabla^2 \phi & = \frac{\phi_{i+1,j}-2\phi_{i,j}+\phi_{i-1,j}}{(\Delta x)^2} + \frac{\phi_{i,j+1}-2\phi_{i,j}+\phi_{i,j-1}}{(\Delta y)^2} + O \left[ (\Delta x)^2,(\Delta y)^2 \right ]
\end{split}
\label{eq:Ec6_16} \tag{6.16}
\end{equation*}

El anterior esquema genera lo que se denomina como *molécula de cinco puntos* o *fórmula de cinco puntos*

 Fuente: Propio

## Discretización de ecuaciones por el método de Diferencias Finitas

### Estado estacionario

[Ecuaciones en estado estacionario](https://en.wikipedia.org/wiki/Steady_state), son ecuaciones que no dependel del tiempo. A manera de ejemplo, considérese la ecuación de Laplace en $2D$ discretizada por la fórmula de cinco puntos en una región $\Omega$ cuadrada con condiciones de contorno Dirichlet $\phi(x,y)=\Theta(x,y)$ en la frontera $\delta \Omega$

\begin{equation*}
\begin{split}
\nabla^2 \phi & = \frac{\partial^2 \phi}{\partial x^2} + \frac{\partial^2 \phi}{\partial y^2}=0
\end{split}
\label{eq:Ec6_17} \tag{6.17}
\end{equation*}

Discretizando esta ecuación con la ecuación [(6.16)](#6_16), y reordenando los términos, se llega a:

\begin{equation*}
\begin{split}
\phi_{i+1,j}+\phi_{i-1,j}-2(1+\beta^2)\phi_{i,j}+\beta^2(\phi_{i,j+1}+\phi_{i,j-1})=0
\end{split}
\label{eq:Ec6_18} \tag{6.18}
\end{equation*}

donde $\beta=\frac{\Delta x}{\Delta y}$.

Teniendo una discretización del dominio como se muestra en la figura:

 Fuente: Propio

La ecuación [(6.18)](#Ec6_18) será válida para todos los puntos discretos $(i,j)$ al interior de la región (nodos en color naranja). Dando valores a los subíndices $(i,j)$ desde $0$ hasta $4$ y reemplazando en la ecuación [(6.18)](#Ec6_18), se llega al siguente sistema de ecuaciones:

\begin{equation*}
\begin{split}
(1,1) : \phi_{2,1} + \phi_{0,1} - 2(1+\beta^2)\phi_{1,1} + \beta^2(\phi_{1,2} + \phi_{1,0})=0 \\
(2,1) : \phi_{3,1} + \phi_{1,1} - 2(1+\beta^2)\phi_{2,1} + \beta^2(\phi_{2,2} + \phi_{2,0})=0 \\
(3,1) : \phi_{4,1} + \phi_{2,1} - 2(1+\beta^2)\phi_{3,1} + \beta^2(\phi_{3,2} + \phi_{3,0})=0 \\
(1,2) : \phi_{2,2} + \phi_{0,2} - 2(1+\beta^2)\phi_{1,2} + \beta^2(\phi_{1,3} + \phi_{1,1})=0 \\
(2,2) : \phi_{3,2} + \phi_{1,2} - 2(1+\beta^2)\phi_{2,2} + \beta^2(\phi_{2,3} + \phi_{2,1})=0 \\
(3,2) : \phi_{4,2} + \phi_{2,2} - 2(1+\beta^2)\phi_{3,2} + \beta^2(\phi_{3,3} + \phi_{3,1})=0 \\
(1,3) : \phi_{2,3} + \phi_{0,3} - 2(1+\beta^2)\phi_{1,3} + \beta^2(\phi_{1,4} + \phi_{1,2})=0 \\
(2,3) : \phi_{3,3} + \phi_{1,3} - 2(1+\beta^2)\phi_{2,3} + \beta^2(\phi_{2,4} + \phi_{2,2})=0 \\
(3,3) : \phi_{4,3} + \phi_{2,3} - 2(1+\beta^2)\phi_{3,3} + \beta^2(\phi_{3,4} + \phi_{3,2})=0
\end{split}
\label{eq:Ec6_19} \tag{6.19}
\end{equation*}

Expresando el anterior sistema de ecuaciones lineales en forma matricial se llega a un sistema $Ax=b$

\begin{align*}
\left[\begin{array}{ccccccccc}
  \gamma &     1  &    0   &\beta^2 &    0    &    0    &    0    &    0    &    0    \\
  1      & \gamma &    1   &    0   & \beta^2 &    0    &    0    &    0    &    0    \\
  0      &     1  & \gamma &    0   &    0    & \beta^2 &    0    &    0    &    0    \\
  \beta^2&     0  &    0   & \gamma &    1    &    0    & \beta^2 &    0    &    0    \\
  0      & \beta^2&    0   &    1   & \gamma  &    1    &    0    & \beta^2 &    0    \\
  0      &     0  & \beta^2&    0   &    1    & \gamma  &    0    &    0    & \beta^2 \\
  0      &     0  &     0  & \beta^2&    0    &    0    & \gamma  &    1    &    0    \\
  0      &     0  &     0  &     0  & \beta^2 &    0    &    1    & \gamma  &    1    \\
  0      &     0  &     0  &     0  &     0   & \beta^2 &    0    &    1    & \gamma
\end{array}\right]
\begin{Bmatrix}
\phi_{1,1}\\
\phi_{2,1}\\
\phi_{3,1}\\
\phi_{1,2}\\
\phi_{2,2}\\
\phi_{3,2}\\
\phi_{1,3}\\
\phi_{2,3}\\
\phi_{3,3}
\end{Bmatrix}
= \begin{Bmatrix}
-\phi_{0,1}-\beta^2\phi_{1,0}\\
-\beta^2\phi_{2,0}\\
-\phi_{4,1}-\beta^2\phi_{3,0}\\
-\phi_{0,2}\\
0\\
-\phi_{4,2}\\
-\phi_{0,3}-\beta^2\phi_{1,4}\\
-\beta^2\phi_{2,4}\\
-\phi_{4,3}-\beta^2\phi_{3,4}
\end{Bmatrix}
\end{align*}

donde $\gamma=-2(1+\beta^2)$

### Ejemplo: ecuación de transporte en estado estacionario en 1D

Resuelva la ecuación de transporte de convección - difusión homogénea en estado estacionario en 1D dada por

$$\frac{d^2\phi}{dx^2}-\beta \frac{d\phi}{dx}=0$$

sometida a las condiciones de contorno

$$\phi(0)=0 \quad \quad \phi(1) = 1$$

Se va a resolver numéricamente esta ecuación utilizando  un esquema de diferencias finitas centrales (Leapfrog). Discretizando la ecuación dada por los esquemas indicados, se llega a:

$$-\frac{1}{h^2} \left[ \phi(x_{i-1})-2\phi(x_i)+\phi(x_{i+1})\right]+\frac{\beta}{2h}\left[ \phi(x_{i+1})-\phi(x_{i-1})\right]=0$$

organizando términos,

$$(2+\beta h)\phi(x_{i-1})-4\phi(x_i)+(2-\beta h)\phi(x_{i+1})=0$$

tomando como tamaño de paso inicialmente el valor de $h=0.1$ y por las condiciones de contorno dadas, se tiene la siguiente distribución de nodos y elementos ("malla"):

 Fuente: Propio

Dando valores a $i$ desde $1$ hasta $9$, se tiene el siguiente conjunto de ecuaciones:

\begin{align*}
\begin{array}{cccc}
i=1:& -4\phi(x_1)+(2-\beta h)\phi(x_2)&=-(2+\beta h)\phi(x_0) \\
i=2:& (2+\beta h)\phi(x_1)-4\phi(x_2)+(2-\beta h)\phi(x_3)&=0 \\
i=3:& (2+\beta h)\phi(x_2)-4\phi(x_3)+(2-\beta h)\phi(x_4)&=0 \\
i=4:& (2+\beta h)\phi(x_3)-4\phi(x_4)+(2-\beta h)\phi(x_5)&=0 \\
i=5:& (2+\beta h)\phi(x_4)-4\phi(x_5)+(2-\beta h)\phi(x_6)&=0 \\
i=6:& (2+\beta h)\phi(x_5)-4\phi(x_6)+(2-\beta h)\phi(x_7)&=0 \\
i=7:& (2+\beta h)\phi(x_6)-4\phi(x_7)+(2-\beta h)\phi(x_8)&=0 \\
i=8:& (2+\beta h)\phi(x_7)-4\phi(x_8)+(2-\beta h)\phi(x_9)&=0 \\
i=9:& (2+\beta h)\phi(x_8)-4\phi(x_9)&=-(2-\beta h)\phi(x_{10})
\end{array}
\end{align*}

los nodos $i=0$ e $i=10$ corresponderán al primer y último nodo de la malla, cuyos valores $\phi(x_0)$ y $\phi(x_{10})$ son dados por las condiciones de contorno

```python
import numpy as np
import matplotlib.pyplot as plt
```

```python
# Establecer las CCs - Izquierda

x0 = 0
u0 = 0

# Establecer las CCs - Derecha

x1 = 1
u1 = 1

```

```python
# Ingreso de datos
n = int(input("Ingrese el número de nodos: "))
```

    Ingrese el número de nodos: 10

```python
beta = float(input("Ingrese el valor de la velocidad: "))
```

    Ingrese el valor de la velocidad: 50

```python
# Inicialización de arreglos
A = np.zeros((n-1, n-1)) # Matriz de coeficientes
b = np.zeros((n-1, 1))   # Vector de térinos independientes
u_lf = np.zeros((n-1, 1))   # Vector de incógnitas
uex = np.zeros((n-1, 1)) # Vector con la solución exacta
```

```python
# cálculos iniciales
h = (x1 - x0) / n  # Tamaño de paso Dx
x = np.linspace(x0, x1, n + 1)
```

```python
# Ensamblaje de la matriz de coeficientes

for i in range(1, n-1):
    A[i-1, i]   = 2 - beta * h
    A[i-1, i-1] = -4
    A[i, i - 1] = 2 + beta * h
    b[i] = 0

A[n-2,n-2] = -4
b[0] = -(2 + beta * h) * u0
b[n-2] = -(2 - beta * h ) * u1
```

```python
# Solución del SEL Au=b
u_lf = np.linalg.solve(A,b)
```

```python
# Adicionando las CC en x0 y x1

u_lf = np.insert(u_lf, 0, u0)
u_lf = np.insert(u_lf, n, u1)
```

```python
# definición de la función exacta
def exacta(x):
    return (np.exp(beta * x) - 1) / (np.exp(beta) - 1)
```

```python
# Graficación
plt.xlabel("x")
plt.ylabel("u")
plt.title("Exacta vs Leapfrog")
plt.plot(x, u_lf, 'r', label="Leapfrog")
plt.plot(x, exacta(x), 'b', label=('Exacta'))
plt.legend(loc='center left')
plt.grid(True)
plt.show()
```

### Ecuaciones No estacionarias

#### Introducción

Las ecuaciones [no estacionarias](https://en.wikipedia.org/wiki/Transient_state) (transient, en inglés) o dependientes del tiempo, exigen el cálculo de la solución en intervalos sucesivos de tiempo

\begin{equation*}
\begin{split}
t_0+\Delta t, t_0 + 2\Delta t, t_0 + 3\Delta t, \ldots, t_f - \Delta t, t_f
\end{split}
\label{eq:Ec6_20} \tag{6.20}
\end{equation*}

Una de las ecuaciones más representativas de este tipo es la [ecuación de calor](https://en.wikipedia.org/wiki/Heat_equation), dada por

\begin{equation*}
\begin{split}
\frac{\partial T}{\partial t}=\alpha\frac{\partial^2T}{\partial x^2}
\end{split}
\label{eq:Ec6_21} \tag{6.21}
\end{equation*}

sometida a las condiciones auxiliares:

- ***Iniciales***

\begin{equation*}
\begin{split}
T_i^0=T(i\Delta x,t_0)=T_{inic}
\end{split}
\label{eq:Ec6_22} \tag{6.22}
\end{equation*}

- ***Contorno:*** tipo Dirichlet

\begin{equation*}
\begin{split}
T(0,t) &= T_0 \\
T(IM,t) &= T_{IM}
\end{split}
\label{eq:Ec6_23} \tag{6.23}
\end{equation*}

#### Discretización de ecuaciones no estacionarias

La ecuación [(6.21)](#Ec6_21) tiene dos variables independientes, $x$ y $t$. Se busca determinar un vector $T^{(n)}$ que contenga las temperaturas, para cada nivel de tiempo $n$, y para todos los nodos de la malla.

 Fuente: Propio

Filas del mismo color representan la discretización espacial en $1D$. La dirección vertical representa el tiempo.

Ahora, discretizando la ecuación [(6.21)](#Ec6_21) en la coordenada espacial en un punto $x_i$, empleando un [esquema de diferencias finitas central para la segunda derivada](#DFC_d2y) dado por la ecuación [(6.7)](#Ec6_7):

\begin{equation*}
\begin{split}
\left. \frac{\partial T}{\partial t} \right|_{i}=\alpha\frac{T_{i-1}-2T_i+T_{i+1}}{(\Delta x)^2} + \mathcal{O}(\Delta x)^2
\end{split}
\label{eq:Ec6_24} \tag{6.24}
\end{equation*}

Para discretizar la ecuación [(6.21)](#Ec6_21) en la coordenada temporal se emplearán dos enfoques diferentes: [explícita e implícita](https://en.wikipedia.org/wiki/Explicit_and_implicit_methods):

#### Discretización explícita

La ecuación [(6.24)](#Ec6_24) discretizada en un nivel de tiempo $n$ toma la forma:

\begin{equation*}
\begin{split}
\left. \frac{\partial T}{\partial t} \right|_{i}^{(n)}=\alpha\frac{T_{i-1}^{(n)}-2T_i^{(n)}+T_{i+1}^{(n)}}{(\Delta x)^2} + \mathcal{O}(\Delta x)^2
\end{split}
\label{eq:Ec6_25} \tag{6.25}
\end{equation*}

Relacionando los valores de $T_i^{(n+1)}$ con los de $T_i^{(n)}$, utilizando un esquema en Diferencias Finitas hacia adelante (Forward) para la primera derivada:

\begin{equation*}
\begin{split}
\left. \frac{\partial T}{\partial t} \right|_{i}^{(n)}=\frac{T_{i}^{(n+1)}-T_{i}^{(n)}}{\Delta t} + \mathcal{O}(\Delta t)
\end{split}
\label{eq:Ec6_26} \tag{6.26}
\end{equation*}

Igualando las ecuaciones [(6.25)](#Ec6_25) y [(6.26)](#Ec6_26), se llega al siguiente esquema:

\begin{equation*}
\begin{split}
\frac{T_{i}^{(n+1)}-T_{i}^{(n)}}{\Delta t} = \alpha\frac{T_{i-1}^{(n)}-2T_i^{(n)}+T_{i+1}^{(n)}}{(\Delta x)^2} + \mathcal{O} \left[ \Delta t, (\Delta x)^2 \right]
\end{split}
\label{eq:Ec6_27} \tag{6.27}
\end{equation*}

Por último, de la anterior ecuación se despeja la variable temporal en el tiempo siguiente, $T_i^{(n+1)}$ y se llega al esquema llamado *Método de Euler explícito*:

\begin{equation*}
\begin{split}
T_{i}^{(n+1)} = T_{i}^{(n)} + \alpha (\Delta t) \frac{T_{i-1}^{(n)}-2T_i^{(n)}+T_{i+1}^{(n)}}{(\Delta x)^2}
\end{split}
\label{eq:Ec6_28} \tag{6.28}
\end{equation*}

El lado derecho de la ecuación [(6.28)](#Ec6_28) solamente depende de los valores de $T$ en el nivel de tiempo $n$.

La molécula de diferencias finitas resultante se observa en la siguiente figura

 Fuente: Propio

***Observaciones:***

- Por las C.Aux. cuando $n=0$ y $x=0$, los valores de $T_i^{(0)}$ son conocidos

- Avanzando en el tiempo se obtiene un sistema de ecuaciones lineales, cuyas incógnitas son los valores de $T^{(n+1)}$.

- Solamente se puede calcular $T^{(n+1)}$ después de conocer los valores en $T^{(n)}$.

***Dificultades:***

- Excluyen valores de C.C. en el nivel de tiempo $n$, que tiene influencia sobre la solución del problema en el nivel de tiempo $n+1$.

 Fuente: Propio

- La solución en los nodos en amarillo no tiene efecto sobre la solución en el punto interno $(x,t)$ en el nivel de tiempo $n+2$.

- La solución en el punto $(x,t)$ es influenciada solamente por las C.C. del nivel de tiempo $n$, y no por el valor de la C.C. en el nivel de tiempo $n+2$.

- Efecto exclusivamente numérico, equivalente a un atraso en la propagación de las C.C. adentro del dominio.

- Físicamente incorrecto.

- Este fenómeno causa grandes errores cuando las C.C. presentan variaciones considerables entre instantes sucesivos de tiempo.

#### Discretización implícita

Proceso de Marcha en el tiempo (igual que en el Explícito). $T^{(1)}$ es calculado a partir de las condiciones iniciales. $T^{(2)}, T^{(3)}, \ldots$, son calculados secuencialmente, precisando de todos los valores del paso anterior. La diferencia está en la forma como es calculado $T^{(n+1)}$. La discretización espacial se realiza en el nivel de tiempo $n+1$.

\begin{equation*}
\begin{split}
\frac{T_{i}^{(n+1)}-T_{i}^{(n)}}{\Delta t} = \alpha\frac{T_{i-1}^{(n+1)}-2T_i^{(n+1)}+T_{i+1}^{(n+1)}}{(\Delta x)^2} + \mathcal{O} \left[ \Delta t, (\Delta x)^2 \right]
\end{split}
\label{eq:Ec6_29} \tag{6.29}
\end{equation*}

reorganizando la ecuación para dejar a un lado los términos evaluados en $T^{(n+1)}$ y al otro lado los términos evaluados en $T^{(n)}$, obteniéndose la expresión del *método de Euler implícito*.

\begin{equation*}
\begin{split}
-sT_{i-1}^{(n+1)}+(1+2s)T_{i}^{(n+1)}-sT_{i+1}^{(n+1)}=T_{i}^{(n)}
\end{split}
\label{eq:Ec6_30} \tag{6.30}
\end{equation*}

donde $s=\frac{\alpha (\Delta t)}{(\Delta x)^2}$

La molécula de diferencias finitas resultante para el método implicito es:

 Fuente: Propio

La ecuación [(6.30)](#Ec6_30) por sí sola no permite determinar el valor de $T_{i}^{(n+1)}$. A cada paso de tiempo aparecen tres incógnitas nuevas: $T_{i-1}^{(n+1)}, T_{i}^{(n+1)}$ y $T_{i+1}^{(n+1)}$. El valor $T_{i}^{(n+1)}$ está definido “implícitamente”.

Escribiendo las ecuaciones asociadas a cada nodo de la malla discretizada

\begin{equation*}
\begin{split}
-sT_{0}^{(n+1)}+(1+2s)T_{1}^{(n+1)}-sT_{2}^{(n+1)} &= T_{1}^{(n)}\\
-sT_{1}^{(n+1)}+(1+2s)T_{2}^{(n+1)}-sT_{3}^{(n+1)} &= T_{2}^{(n)}\\
-sT_{2}^{(n+1)}+(1+2s)T_{3}^{(n+1)}-sT_{4}^{(n+1)} &= T_{3}^{(n)}\\
& \vdots \\
-sT_{IM-3}^{(n+1)}+(1+2s)T_{IM-2}^{(n+1)}-sT_{IM-1}^{(n+1)} &= T_{IM-2}^{(n)}\\
-sT_{IM-2}^{(n+1)}+(1+2s)T_{IM-1}^{(n+1)}-sT_{IM}^{(n+1)} &= T_{IM-1}^{(n)}
\end{split}
\label{eq:Ec6_31} \tag{6.31}
\end{equation*}

aplicando las condiciones de contorno en la ecuación [(6.31)](#Ec6_31)

\begin{equation*}
\begin{split}
(1+2s)T_{1}^{(n+1)}-sT_{2}^{(n+1)} &= T_{1}^{(n)} + sT_{0}^{(n+1)}\\
-sT_{1}^{(n+1)}+(1+2s)T_{2}^{(n+1)}-sT_{3}^{(n+1)} &= T_{2}^{(n)}\\
-sT_{2}^{(n+1)}+(1+2s)T_{3}^{(n+1)}-sT_{4}^{(n+1)} &= T_{3}^{(n)}\\
& \vdots \\
-sT_{IM-3}^{(n+1)}+(1+2s)T_{IM-2}^{(n+1)}-sT_{IM-1}^{(n+1)} &= T_{IM-2}^{(n)}\\
-sT_{IM-2}^{(n+1)}+(1+2s)T_{IM-1}^{(n+1)} &= T_{IM-1}^{(n)} + sT_{IM}^{(n+1)}
\end{split}
\label{eq:Ec6_32} \tag{6.32}
\end{equation*}

representando la anterior ecuación en forma matricial, se tiene

\begin{align*}
\left[\begin{array}{ccccccccc}
  1 + 2s &   -s   &    0   &    0   & \ldots &    0    \\
    -s   & 1 + 2s &    -s  &    0   & \ldots &    0    \\
  0      &   -s   & 1 + 2s &    -s  & \ldots &    0    \\
         &        &        & \ddots &        &         \\
  0      & \ldots &    0   &   -s   & 1+2s   &    -s   \\
  0      & \ldots &    0   &    0   &   -s   &   1+2s
  \end{array}\right]
\begin{Bmatrix}
T_{1}^{(n+1)}\\
T_{2}^{(n+1)}\\
T_{3}^{(n+1)}\\
\ldots\\
T_{IM-2}^{(n+1)}\\
T_{IM-1}^{(n+1)}
\end{Bmatrix}
= \begin{Bmatrix}
T_{1}^{(n)}+ sT_{0}^{(n+1)}\\
T_{2}^{(n)}\\
T_{3}^{(n)}\\
\vdots\\
T_{IM-2}^{(n)}\\
T_{IM-1}^{(n)}+sT_{IM}^{(n+1)}
\end{Bmatrix}
\label{eq:Ec6_33} \tag{6.33}
\end{align*}

#### Resumen

***Métodos explícitos:***

- Las ecuaciones resultantes son independientes, por lo tanto, la solución es directa.

***Métodos implícitos:***

- Las ecuaciones resultantes son acopladas, por lo que se debe resolver un sistema de ecuaciones lineales en cada instante de tiempo!

### Discretización multidimensional

Como ejemplo, tomemos la ecuación de difusión no permanente en 2D

\begin{equation*}
\begin{split}
\frac{\partial \phi}{\partial t} = \alpha \nabla^2 \phi = \alpha \left( \frac{\partial^2 \phi}{\partial x^2} + \frac{\partial^2 \phi}{\partial y^2} \right)
\end{split}
\label{eq:Ec6_34} \tag{6.34}
\end{equation*}

un dominio computacional en 2D, discretizado por una serie de nodos y elementos, se observa en la siguiente figura.

 Fuente: Propio

- Empleando el método de Euler explícito y diferencias finitas centrales de segundo orden, se llega a

\begin{equation*}
\begin{split}
\frac{\phi_{i,j}^{(n+1)}-\phi_{i,j}^{(n)}}{\Delta t} = \alpha \frac{\phi_{i+1,j}^{(n)}-2\phi_{i,j}^{(n)}+\phi_{i-1,j}^{(n)}}{(\Delta x)^2} + \alpha \frac{\phi_{i,j+1}^{(n)}-2\phi_{i,j}^{(n)}+\phi_{i,j-1}^{(n)}}{(\Delta y)^2} + \mathcal{O} \left[(\Delta t),(\Delta x)^2,(\Delta y)^2\right]
\end{split}
\label{eq:Ec6_35} \tag{6.35}
\end{equation*}

organizando términos,

\begin{equation*}
\begin{split}
\phi_{i,j}^{(n+1)}=\phi_{i,j}^{(n)} + s_x \left( \phi_{i+1,j}^{(n)} - 2\phi_{i,j}^{(n)} + \phi_{i-1,j}^{(n)} \right) + s_y \left( \phi_{i,j+1}^{(n)} - 2\phi_{i,j}^{(n)} + \phi_{i,j-1}^{(n)} \right)
\end{split}
\label{eq:Ec6_36} \tag{6.36}
\end{equation*}

- Ahora empleando el método de Euler implícito y diferencias finitas centrales de segundo orden,

\begin{equation*}
\begin{split}
-s_x \left( \phi_{i+1,j}^{(n+1)} + \phi_{i-1,j}^{(n+1)} \right) + \left[1+2\left( s_x+s_y \right) \right] \phi_{i,j}^{(n+1)} - s_y \left( \phi_{i,j+1}^{(n+1)} + \phi_{i,j-1}^{(n+1)} \right) = \phi_{i,j}^{(n)}
\end{split}
\label{eq:Ec6_37} \tag{6.37}
\end{equation*}

donde $s_x=\frac{\alpha (\Delta t)}{(\Delta x)^2}$ y $s_y=\frac{\alpha (\Delta t)}{(\Delta y)^2}$

## Consistencia, Convergencia y Estabilidad

### Introducción

La solución calculada se aproxima, de alguna forma, a la solución “real” de la EDP?

El *Error Local de Truncamiento (ELT)* influencia en la calidad de la aproximación numérica. Cuándo y sobre qué condiciones la solución de las ecuaciones lineales es representativa de la solución real de la EDP?

Depende de principalmente de tres elementos: [Consistencia, Estabilidad y Convergencia](https://en.wikipedia.org/wiki/Numerical_methods_for_ordinary_differential_equations#Analysis)

- Consistencia de las ecuaciones de diferencias finitas.

- Estabilidad y convergencia del método numérico empleado.

### Consistencia

En la discretización, el $ELT \rightarrow 0$ cuando $\Delta x, \Delta t \rightarrow 0$, es decir, si

\begin{equation*}
\begin{split}
\lim \limits_{\Delta x \to 0}\frac{\phi_{i+1}-\phi_i}{\Delta x}=\frac{\partial \phi}{\partial x}
\end{split}
\label{eq:Ec6_38} \tag{6.38}
\end{equation*}

Verificando:

- Se substituye la expansión en series de Taylor en la E.D.F.

- Se hace que $\Delta x, \Delta t \rightarrow 0$

- $ELT \rightarrow 0$?, la discretización es consistente con la EDP.

- $ELT\nrightarrow 0$?, la discretización no es consistente con la EDP.

 Fuente: Propio

La *consistencia* es una condición necesaria para la *convergencia*.

### Convergencia

*Cómo afecta la discretización hecha a la EDP en un determinado número arbitrario de pasos de tiempo?*

Si la solución numérica se aproxima a la solución exacta de la EDP, conforme $\Delta x, \Delta t \rightarrow 0$, se dice que el método es convergente.

 Fuente: Propio

### Estabilidad

Un método numérico es estable si cualquier error o perturbación en la solución no se amplifican sin límites. Ese crecimiento es relativo al método numérico exclusivamente y no a la física del problema. La estabilidad trata sobre el crecimiento, o disminución, de los errores introducidos en los cálculos.

- En los métodos no–permanentes, la estabilidad garantiza que la solución numérica sea limitada.

- Para los métodos iterativos de la solución de sistemas lineales un método estable es aquel que no diverge conforme las iteraciones avanzan.

***Cuáles son estos errores?***

- Condiciones de contorno e iniciales aproximadas de forma incorrecta

- Acumulación de errores de redondeo durante los cálculos.

***Cómo se evitan?***

- Correcta discretización de las condiciones auxiliares.

- No puede ser evitado, pero sí controlado.

***Con relación a la estabilidad los métodos numéricos pueden clasificarse en:***

- ***Condicionalmente estables:*** Deben satisfacer alguna condición de estabilidad. Métodos explícitos.

- ***Incondicionalmente estables:*** No necesitan satisfacer condiciones de estabilidad. Métodos Implícitos.

- ***Incondicionalmente inestables:**** No existen valores de $\Delta t$ que permitan soluciones estables. Deben evitarse.

#### Teorema de Lax

El [Teorema de Lax](https://en.wikipedia.org/wiki/Lax_equivalence_theorem) (en honor al matemático [Peter Lax](https://en.wikipedia.org/wiki/Peter_Lax)) establece que:

> *Dado un problema de valores iniciales bien planteado matemáticamente y una aproximación en diferencias finitas que satisface la condición de Consistencia, entonces la Estabilidad es condición necesaria y suficiente para la Convergencia.*

#### Condición CFL

la condición de [Courant-Friedrichs-Lewy](https://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition) (condición CFL) es una condición de convergencia de ecuaciones diferenciales en derivadas parciales solucionadas mediante ciertos algortimos (no confundir con estabilidad numérica). Como consecuencia de esta condición, el paso de tiempo debe ser inferior a un cierto valor, si no, la simulación producirá resultados incorrectos. Está condición se llama así en honor a [Richard Courant](https://en.wikipedia.org/wiki/Richard_Courant), [Kurt Friedrichs](https://en.wikipedia.org/wiki/Kurt_Otto_Friedrichs) y [Hans Lewy](https://en.wikipedia.org/wiki/Hans_Lewy) que la describieron en un [artículo en 1928](https://web.stanford.edu/class/cme324/classics/courant-friedrichs-lewy.pdf).

La condición *CFL* se representa comúnmente para esquemas de advección puros (es decir ignorando los términos de difusión y reacción) como:

\begin{equation*}
\begin{split}
\frac{u\cdot \Delta t}{\Delta x} < C
\end{split}
\label{eq:Ec6_39} \tag{6.39}
\end{equation*}

En un caso bidimensional la ecuación anterior se transforma en:

\begin{equation*}
\begin{split}
\frac{u_{x}\cdot \Delta t}{\Delta x} + \frac{u_{y}\cdot \Delta t}{\Delta y} < C
\end{split}
\label{eq:Ec6_40} \tag{6.40}
\end{equation*}

La condición CFL puede ser muy limitante para el intervalo de tiempo $\Delta t$, hasta el punto que para ciertas ecuaciones diferenciales en derivadas parciales de cuarto orden es:

\begin{equation*}
\begin{split}
\frac{\Delta t}{\Delta x^4} < C
\end{split}
\label{eq:Ec6_41} \tag{6.41}
\end{equation*}

por lo que se utilizan mejor métodos implícitos para evitarla.

#### Relación Consistencia - Convergencia - Estabilidad

 Fuente: Propio

### Esquema Upwind

Con la intención de suavizar las oscilaciones presentes debido al término convectivo, se planteará un equema numérico tipo [upwind](https://en.wikipedia.org/wiki/Upwind_scheme), que consiste principalmente en adicionar una cierta cantidad de difusión extra al término difusivo teniendo en cuenta la dirección del flujo. El esquema de diferenciación [upwind](https://en.wikipedia.org/wiki/Upwind_differencing_scheme_for_convection) supera la incapacidad del esquema de diferenciación [central](#Ej01_TransportEq). Este esquema está desarrollado para flujos convectivos fuertes con efectos de difusión suprimidos.

Volviendo al enunciado del ejemplo visto en el numeral [3.2](#Ej01_TransportEq), considere la ecuación de transporte de convección - difusión homogénea en estado estacionario en 1D dada por

$$\frac{d^2\phi}{dx^2}-\beta \frac{d\phi}{dx}=0$$

sometida a las condiciones de contorno

$$\phi(0)=0 \quad \quad \phi(1) = 1$$

Empleando alguno de los esquemas de discretizacion para el término de la primera derivada (advectivo) vistos,

\begin{equation*}
\begin{split}
\text{Forward:} \quad\quad \phi'(x_i) = \frac{\phi(x_{i+1})-\phi(x_{i})}{h}
\end{split}
\label{eq:Ec6_42} \tag{6.42}
\end{equation*}

o

\begin{equation*}
\begin{split}
\text{Backward:} \quad\quad \phi'(x_i) = \frac{\phi(x_{i})-\phi(x_{i-1})}{h}
\end{split}
\label{eq:Ec6_43} \tag{6.43}
\end{equation*}

Si se aplica un [análisis de estabilidad de VonNeumann](https://en.wikipedia.org/wiki/Von_Neumann_stability_analysis) se encontrará que el esquema de discretización es inestable.

Una posible solución para intentar evitar las dificultades encontradas en tales formulaciones es el [esquema Upwind](https://en.wikipedia.org/wiki/Upwind_scheme). La Ec. [(6.42)](#Ec6_42) puede estabilizarse sustituyendo la discretización espacial hacia adelante adelante por un esquema de discretización hacia atrás, siempre que la velocidad $\beta>0$. Si $\beta<0$ es negativo, se debe utilizar un esquema de diferencias hacia adelante para asegurar la estabilidad. En esta formulación el término de difusión permanece sin cambios y solo el término convectivo (en forma conservadora) se calcula de la siguiente manera:

\begin{equation*}
\begin{split}
-\beta\frac{\phi(x_{i})-\phi(x_{i-1})}{h} + \text{Término viscoso, si } \beta > 0
\end{split}
\label{eq:Ec6_44} \tag{6.44}
\end{equation*}

o

\begin{equation*}
\begin{split}
-\beta\frac{\phi(x_{i+1})-\phi(x_{i})}{h} + \text{Término viscoso, si } \beta < 0
\end{split}
\label{eq:Ec6_45} \tag{6.45}
\end{equation*}

Una formulación en diferencias finitas de una ecuación de flujo posee la propiedad de transporte si el efecto de una perturbación se convecta (advecta) solo en la dirección de la velocidad.

### Solución de la ecuación de convección-difusión en estado estacionario en 1D empleando el esquema Upwind

En la sección [3.2](#Ej01_TransportEq) se presentó la ecuación de transporte de convección - difusión homoegénea en estado estacionario en 1D dada por

$$-\frac{d^2\phi}{dx^2}+\beta \frac{d\phi}{dx}=0$$

sometida a las condiciones de contorno

$$\phi(0)=0 \quad \quad \phi(1) = 1$$

y se observó que para valores de $\beta$ altos ($>50$ en el ejemplo) se presentaron oscilaciones debidas a la incapacidad del esquema de discretización empleado (de segundo orden tanto para el término convectivo como  para el término difusivo) de capturarlas. Ya con el conocimiento de los elementos de *estabilidad, consistencia* y *convergencia* dados en el numeral anterior, se pueden plantear esquemas de discretización que cumplan con dichas restricciones.

Ahora, se va a resolver numéricamente esta ecuación utilizando  un esquema de diferencias finitas centrales para el término difusivo y hacia atrás, forward, para el término advectivo (según lo indicado para el esquema *Upwind*). Discretizando la ecuación dada por los esquemas indicados, se llega a:

$$\frac{1}{h^2} \left[ \phi(x_{i-1})-2\phi(x_i)+\phi(x_{i+1})\right]-\frac{\beta}{h}\left[ \phi(x_{i})-\phi(x_{i-1})\right]=0$$

organizando términos,

$$(1+\beta h)\phi(x_{i-1})-(2+\beta h)\phi(x_i)+\phi(x_{i+1})=0$$

Se realiza el mismo procedimiento anterior para determinar el sistema de ecuaciones resultantes. A continuación se presenta una codificación del esquema *Upwind* propuesta por el autor.

```python
# Esquema Upwind

import numpy as np
import matplotlib.pyplot as plt

def exacta(x):
    return (np.exp(beta * x) - 1) / (np.exp(beta) - 1)

# establecimiento de las condiciones de contorno a la izquierda

a0 = 0
u0 = 0

# establecimiento de las condiciones de contorno a la derecha

a1 = 1
u1 = 1

# Ingreso de datos
n = int(input("Ingrese la cantidad de nodos: "))
beta = float(input("ingrese el valor de la velocidad, beta: "))

# Inicialización de los diferentes arreglos a emplear
A = np.zeros((n-1, n-1)) # matriz de coeficientes
b = np.zeros((n-1, 1))   # vector de términos independietes
u_upw = np.zeros((n-1, 1))   # vector de incógnitas
uex = np.zeros((n-1,1))  # vector de la solución exacta

h = (a1 - a0) / n            # establece el tamaño de paso Delta x
x = np.linspace(a0, a1, n+1)  # Genera n+1 puntos entre a0 y a1 igualmente espaciados

# ensamblaje de la matriz de coeficientes y establecimiento de las c.c.
for i in range(1,n-1):
    A[i-1, i] =  1               # phi{i+1}
    A[i-1, i-1] = -2 - beta * h  # phi{i}
    A[i, i-1] = 1 + beta * h     # phi{i-1}
    b[i] = 0

A[n-2,n-2] = -2 - beta * h
b[0]   = -(1 + beta * h) * u0
b[n-2] = -1 * u1

# Solución del SEL Au = b
u_upw = np.linalg.solve(A, b)

# Adicionando las CC
u_upw = np.insert(u_upw,0,u0) # c.c. a laizquierda
u_upw = np.insert(u_upw,n,u1) # c.c. a la derecha

plt.xlabel (r"$x$")
plt.ylabel (r"$\phi(x)$")
plt.title (r"Exacta vs Upwind")
plt.plot(x, u_upw,'r', label='Upwind')
plt.plot(x, exacta(x),'b', label='Exacta')
plt.legend(loc='center left')
plt.grid(True)
plt.show()
```

### Esquema de Viscosidad artificial

El esquema de [viscosidad artificial](https://nptel.ac.in/courses/112/104/112104030/) consiste de la inclusión, de forma consistente, de un término que adiciona difusión artificial a la ecuación, y está dado como:

\begin{equation*}
\begin{split}
-\beta\frac{d\phi}{dx}+\frac{d^2\phi}{dx^2}+\nu_{ex}\frac{d^2\phi}{dx^2}=0
\end{split}
\label{eq:Ec6_46} \tag{6.46}
\end{equation*}

juntando términos, queda

\begin{equation*}
\begin{split}
-\beta\frac{d\phi}{dx}+(1+\nu_{ex}) \frac{d^2\phi}{dx^2}=0
\end{split}
\label{eq:Ec6_47} \tag{6.47}
\end{equation*}

donde $\nu_{ex}=\frac{1}{2}\beta h$

Empleando un esquema de discretización de diferencias finitas central para la segunda derivada y backward para el término de la primera derivada, como en upwind, se llega al siguiente esquema iterativo

\begin{equation*}
\begin{split}
-(2+\beta h) \phi_{i+1}+4(1+\beta h) \phi_{i}-(2+3\beta h) \phi_{i-1}
\end{split}
\label{eq:Ec6_48} \tag{6.48}
\end{equation*}

```python
# Esquema Viscosidad artificial

import numpy as np
import matplotlib.pyplot as plt

def exacta(x):
    return (np.exp(beta * x) - 1) / (np.exp(beta) - 1)

# establecimiento de las condiciones de contorno a la izquierda

a0 = 0
u0 = 0

# establecimiento de las condiciones de contorno a la derecha

a1 = 1
u1 = 1

# Ingreso de datos
n = int(input("Ingrese la cantidad de nodos: "))
beta = float(input("ingrese el valor de la velocidad, beta: "))

# Inicialización de los diferentes arreglos a emplear
A = np.zeros((n-1, n-1)) # matriz de coeficientes
b = np.zeros((n-1, 1))   # vector de términos independietes
u_visc = np.zeros((n-1, 1))   # vector de incógnitas
uex = np.zeros((n-1,1))  # vector de la solución exacta

h = (a1 - a0) / n            # establece el tamaño de paso Delta x
x = np.linspace(a0, a1, n+1)  # Genera n+1 puntos entre a0 y a1 igualmente espaciados
bh = beta * h

# ensamblaje de la matriz de coeficientes y establecimiento de las c.c.
for i in range(1,n-1):
    A[i-1, i] =  -(2 + bh)      # phi{i+1}
    A[i-1, i-1] = 4 * (1 + bh)  # phi{i}
    A[i, i-1] = -(2 + 3 * bh)   # phi{i-1}
    b[i] = 0

A[n-2,n-2] = 4 * (1 + bh)
b[0]   = (2 + 3 * bh) * u0
b[n-2] = (2 + bh) * u1

# Solución del SEL Au = b
u_visc = np.linalg.solve(A, b)

# Adicionando las CC
u_visc = np.insert(u_visc,0,u0) # c.c. a laizquierda
u_visc = np.insert(u_visc,n,u1) # c.c. a la derecha

plt.xlabel (r"$x$")
plt.ylabel (r"$\phi(x)$")
plt.title (r"Exacta vs Viscosidad artificial")
plt.plot(x, u_visc,'r', label='Viscosidad artificial')
plt.plot(x, exacta(x),'b', label='Exacta')
plt.legend(loc='center left')
plt.grid(True)
plt.show()
```

Ahoroa, visualizando las tres soluciones en una única gráfica

```python
plt.xlabel (r"$x$")
plt.ylabel (r"$\phi(x)$")
plt.title (r"Exacta vs Esquemas de DFF")
plt.plot(x, exacta(x),'b', label='Exacta')
plt.plot(x, u_lf,'g', label='Leapfrog')
plt.plot(x, u_upw,'k', label='Upwind')
plt.plot(x, u_visc,'r', label='Viscosidad artificial')
plt.legend(loc='center left')
plt.grid(True)
plt.show()
```

### Ecuación de convección difusión dependiente del tiempo

Sea la ecuación de [Convección lineal](https://en.wikipedia.org/wiki/Advection) en 1D:

$$\frac{d\phi}{dt}+c \frac{d\phi}{dx}=0$$

Determine el esquema de diferencias finitas empleando:

- Euler Explícito

- Euler Implícito

y realice un código en python que resuelva dicha ecuación, sometida a las siguientes condiciones de contorno e iniciales, para $x\in [0,2]$:

$$\phi(x_i^{(0)})=2; \quad 0.5 \le x_i \le 1$$

y

$$\phi(x_i^{(0)})=1; \quad \text{ en el resto de puntos } x_i $$

Esta es la llamada función "sombrero", que gráficamente quedaría:

 Fuente: Propio

```python
import numpy as np
import matplotlib.pyplot as plt
```

```python
# datos para la discretización en el espacio
a = 0 #float(input("Ingrese el valor del lado izquierdo: "))
b = 2 #float(input("Ingrese el valor del lado derecho: "))
nx = int(input("Ingrese la cantidad de subdivisiones del espacio: "))
dx = (b - a) / nx

# datos para al discretización en el tiempo
t0 = 0.0 #float(input("Ingrese el valor del tiempo inicial: ")) # t0 = 0
tf = 0.75 #float(input("Ingrese el valor del tiempo final: "))   # t1 = 0.75

# condición CFL
beta = 1.0               #Velocidad de la onda
C = 0.5                  # parámetro CFL
dt = C * dx / beta       # tamaño paso de tiempo
nt = int((tf - t0)/dt)   # cantidad de pasos de tiempo
```

    Ingrese la cantidad de subdivisiones del espacio: 41

```python
# generación de las C.Is.
T = np.ones(nx)
T0 = np.ones(nx)
T[int(0.5/dx):int(1/dx + 1)] = 2
T0[:] = T[:]
x = np.linspace(a,b,nx)
plt.plot(x,T);
```

```python
# solución explícita
Tn = np.ones(nx)
s = beta * dt / dx

for n in range(nt):
    Tn[:] = T[:]
    for i in range(1,nx):
        T[i] = Tn[i] - s * (Tn[i] - Tn[i-1])

plt.plot(x, T0, x, T);
```

```python
import numpy as np
import matplotlib.pyplot as plt

# datos para la discretización en el espacio
a = 0 #float(input("Ingrese el valor del lado izquierdo: "))
b = 2 #float(input("Ingrese el valor del lado derecho: "))
nx = int(input("Ingrese la cantidad de subdivisiones del espacio: "))
dx = (b - a) / nx

# datos para al discretización en el tiempo
t0 = 0.0 #float(input("Ingrese el valor del tiempo inicial: ")) # t0 = 0
tf = 0.75 #float(input("Ingrese el valor del tiempo final: "))   # t1 = 0.75

# condición CFL
beta = 1.0               #Velocidad de la onda
C = 0.1                  # parámetro CFL
dt = C * dx / beta       # tamaño paso de tiempo
nt = int((tf - t0)/dt)   # cantidad de pasos de tiempo

# generación de las C.Is.
T = np.ones(nx)
T0 = np.ones(nx)
T[int(0.5/dx):int(1/dx + 1)] = 2
T0[:] = T[:]
x = np.linspace(a,b,nx)
plt.plot(x,T);

# solución explícita
Tn = np.ones(nx)
s = beta * dt / dx

for n in range(nt):
    Tn[:] = T[:]
    for i in range(1,nx):
        T[i] = Tn[i] - s * (Tn[i] - Tn[i-1])

plt.plot(x, T0, x, T);
```

### Solución implícita

```python
import numpy as np
import matplotlib.pyplot as plt

# datos de entrada
nx = int(input("Ingrese la cantidad de divisiones espacil, nx: "))
nt = int(input("Ingrese la cantidad de pasos de tiempo, nt: "))

A = np.zeros((nx, nx)) # matriz de coeficientes

# datos para la discretización en el espacio
a = 0 #float(input("Ingrese el valor del lado izquierdo: "))
b = 2 #float(input("Ingrese el valor del lado derecho: "))
dx = (b - a) / nx

# datos para al discretización en el tiempo
t0 = 0.0 #float(input("Ingrese el valor del tiempo inicial: ")) # t0 = 0
tf = 0.750 #float(input("Ingrese el valor del tiempo final: "))   # t1 = 0.75
dt = (tf - t0) / nt

# generación de las C.Is.
T = np.ones(nx)
T0 = np.ones(nx)
T[int(0.5/dx):int(1/dx + 1)] = 2
T0[:] = T[:]
x = np.linspace(a,b,nx)
plt.plot(x,T);

# solución implícita
c = 1.0 # coeficiente de convección
Tn = np.ones(nx)
s = c * dt / dx

# ensamblaje de la matriz de coeficientes, A
for i in range(nx):
    A[i, i] = 1 + s  # phi{i}

for i in range(nx-1):
    A[i+1, i] = -s   # phi{i+1}

# Solución del SEL Au = b
for i in range(nt):
    Tn[:] = T[:]
    T = np.linalg.solve(A, Tn)

    # Adicionando las CC
    T[0] = 1.0 # c.c. a laizquierda
    T[nx-1]= 1.0 # c.c. a la derecha

plt.plot(x, T0, x, T);
```

```python

```