# Homework and bake-off: word-level entailment with neural networks

```python
__author__ = "Christopher Potts"
__version__ = "CS224u, Stanford, Fall 2020"
```

## Contents

1. [Overview](#Overview)
1. [Set-up](#Set-up)
1. [Data](#Data)
1. [Baseline](#Baseline)
  1. [Representing words: vector_func](#Representing-words:-vector_func)
  1. [Combining words into inputs: vector_combo_func](#Combining-words-into-inputs:-vector_combo_func)
  1. [Classifier model](#Classifier-model)
  1. [Baseline results](#Baseline-results)
1. [Homework questions](#Homework-questions)
  1. [Hypothesis-only baseline [2 points]](#Hypothesis-only-baseline-[2-points])
  1. [Alternatives to concatenation [2 points]](#Alternatives-to-concatenation-[2-points])
  1. [A deeper network [2 points]](#A-deeper-network-[2-points])
  1. [Your original system [3 points]](#Your-original-system-[3-points])
1. [Bake-off [1 point]](#Bake-off-[1-point])

## Overview

The general problem is word-level natural language inference. Training examples are pairs of words $(w_{L}, w_{R}), y$ with $y = 1$ if $w_{L}$ entails $w_{R}$, otherwise $0$.

The homework questions below ask you to define baseline models for this and develop your own system for entry in the bake-off, which will take place on a held-out test-set distributed at the start of the bake-off. (Thus, all the data you have available for development is available for training your final system before the bake-off begins.)

## Set-up

See [the first notebook in this unit](nli_01_task_and_data.ipynb) for set-up instructions.

```python
from collections import defaultdict
import json
import numpy as np
import os
import pandas as pd
from torch_shallow_neural_classifier import TorchShallowNeuralClassifier
import nli
import utils
```

```python
DATA_HOME = 'data'

NLIDATA_HOME = os.path.join(DATA_HOME, 'nlidata')

wordentail_filename = os.path.join(
    NLIDATA_HOME, 'nli_wordentail_bakeoff_data.json')

GLOVE_HOME = os.path.join(DATA_HOME, 'glove.6B')
```

## Data

I've processed the data into a train/dev split that is designed to put some pressure on our models to actually learn these semantic relations, as opposed to exploiting regularities in the sample.

The defining feature of the dataset is that the `train` and `dev` __vocabularies__ are disjoint. That is, if a word `w` appears in a training pair, it does not occur in any text pair. It follows from this that there are also no word-pairs shared between train and dev, as you would expect. This should require your models to learn abstract relationships, as opposed to memorizing incidental properties of individual words in the dataset.

```python
with open(wordentail_filename) as f:
    wordentail_data = json.load(f)
```

The keys are the splits plus a list giving the vocabulary for the entire dataset:

```python
wordentail_data.keys()
```

    dict_keys(['dev', 'train', 'vocab'])

```python
wordentail_data['train'][: 5]
```

    [[['abode', 'house'], 1],
     [['abortion', 'anaemia'], 0],
     [['abortion', 'aneurysm'], 0],
     [['abortion', 'blindness'], 0],
     [['abortion', 'deafness'], 0]]

```python
nli.get_vocab_overlap_size(wordentail_data)
```

    0

Because no words are shared between `train` and `dev`, no pairs are either:

```python
nli.get_pair_overlap_size(wordentail_data)
```

    0

Here is the label distribution:

```python
pd.DataFrame(wordentail_data['train'])[1].value_counts()
```

    0    7000
    1    1283
    Name: 1, dtype: int64

This is a challenging label distribution – there are more than 5 times as more non-entailment cases as entailment cases.

## Baseline

Even in deep learning, __feature representation is vital and requires care!__ For our task, feature representation has two parts: representing the individual words and combining those representations into a single network input.

### Representing words: vector_func

Let's consider two baseline word representations methods:

1. Random vectors (as returned by `utils.randvec`).
1. 50-dimensional GloVe representations.

```python
def randvec(w, n=50, lower=-1.0, upper=1.0):
    """Returns a random vector of length `n`. `w` is ignored."""
    return utils.randvec(n=n, lower=lower, upper=upper)
```

```python
def load_glove50():
    glove_src = os.path.join(GLOVE_HOME, 'glove.6B.50d.txt')
    # Creates a dict mapping strings (words) to GloVe vectors:
    GLOVE = utils.glove2dict(glove_src)
    return GLOVE

GLOVE = load_glove50()

def glove_vec(w):
    """Return `w`'s GloVe representation if available, else return
    a random vector."""
    return GLOVE.get(w, randvec(w, n=50))
```

### Combining words into inputs: vector_combo_func

Here we decide how to combine the two word vectors into a single representation. In more detail, where `u` is a vector representation of the left word and `v` is a vector representation of the right word, we need a function `vector_combo_func` such that `vector_combo_func(u, v)` returns a new input vector `z` of dimension `m`. A simple example is concatenation:

```python
def vec_concatenate(u, v):
    """Concatenate np.array instances `u` and `v` into a new np.array"""
    return np.concatenate((u, v))
```

`vector_combo_func` could instead be vector average, vector difference, etc. (even combinations of those) – there's lots of space for experimentation here; [homework question 2](#Alternatives-to-concatenation-[2-points]) below pushes you to do some exploration.

### Classifier model

For a baseline model, I chose `TorchShallowNeuralClassifier`:

```python
net = TorchShallowNeuralClassifier(early_stopping=True)
```

### Baseline results

The following puts the above pieces together, using `vector_func=glove_vec`, since `vector_func=randvec` seems so hopelessly misguided for our problem!

```python
baseline_experiment = nli.wordentail_experiment(
    train_data=wordentail_data['train'],
    assess_data=wordentail_data['dev'],
    model=net,
    vector_func=glove_vec,
    vector_combo_func=vec_concatenate)
```

    Stopping after epoch 32. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 2.5276186764240265

                  precision    recall  f1-score   support

               0      0.866     0.946     0.904      1732
               1      0.463     0.243     0.318       334

        accuracy                          0.832      2066
       macro avg      0.665     0.594     0.611      2066
    weighted avg      0.801     0.832     0.809      2066

## Homework questions

Please embed your homework responses in this notebook, and do not delete any cells from the notebook. (You are free to add as many cells as you like as part of your responses.)

### Hypothesis-only baseline [2 points]

During our discussion of SNLI and MultiNLI, we noted that a number of research teams have shown that hypothesis-only baselines for NLI tasks can be remarkably robust. This question asks you to explore briefly how this baseline affects our task.

For this problem, submit two functions:

1. A `vector_combo_func` function called `hypothesis_only` that simply throws away the premise, using the unmodified hypothesis (second) vector as its representation of the example.

1. A function called `run_hypothesis_only_evaluation` that does the following:
    1. Loops over the two `vector_combo_func` values `vec_concatenate` and `hypothesis_only`, calling `nli.wordentail_experiment` to train on the 'train' portion and assess on the 'dev' portion, with `glove_vec` as the `vector_func`. So that the results are consistent, use an `sklearn.linear_model.LogisticRegression` with default parameters as the model.
    1. Returns a `dict` mapping `function_name` strings to the 'macro-F1' score for that pair, as returned by the call to `nli.wordentail_experiment`. (Tip: you can get the `str` name of, e.g., `hypothesis_only` with `hypothesis_only.__name__`.)

The functions `test_hypothesis_only` and `test_run_hypothesis_only_evaluation` will help ensure that your functions have the desired logic.

```python
def hypothesis_only(u, v):
    pass
    ##### YOUR CODE HERE
    return v

def run_hypothesis_only_evaluation():
    pass
    ##### YOUR CODE HERE
    from sklearn.linear_model import LogisticRegression
    vec_combo_funcs = (vec_concatenate, hypothesis_only)
    res = {}
    for vec_combo_func in vec_combo_funcs:
      baseline_experiment = nli.wordentail_experiment(
        train_data=wordentail_data['train'],
        assess_data=wordentail_data['dev'],
        model=LogisticRegression(),
        vector_func=glove_vec,
        vector_combo_func=vec_combo_func)
      res[vec_combo_func.__name__] =  baseline_experiment['macro-F1']

    return res

#run_hypothesis_only_evaluation()
```

```python
def test_hypothesis_only(hypothesis_only):
    v = hypothesis_only(1, 2)
    assert v == 2
```

```python
test_hypothesis_only(hypothesis_only)
```

```python
def test_run_hypothesis_only_evaluation(run_hypothesis_only_evaluation):
    results = run_hypothesis_only_evaluation()
    assert all(x in results for x in ('hypothesis_only', 'vec_concatenate')), \
        ("The return value of `run_hypothesis_only_evaluation` does not "
         "have the intended kind of keys.")
    assert isinstance(results['vec_concatenate'], float), \
        ("The values of the `run_hypothesis_only_evaluation` result "
         "should be floats.")
```

```python
test_run_hypothesis_only_evaluation(run_hypothesis_only_evaluation)
```

                  precision    recall  f1-score   support

               0      0.865     0.953     0.907      1732
               1      0.481     0.228     0.309       334

        accuracy                          0.835      2066
       macro avg      0.673     0.590     0.608      2066
    weighted avg      0.803     0.835     0.810      2066

                  precision    recall  f1-score   support

               0      0.855     0.971     0.909      1732
               1      0.495     0.147     0.226       334

        accuracy                          0.838      2066
       macro avg      0.675     0.559     0.568      2066
    weighted avg      0.797     0.838     0.799      2066

### Alternatives to concatenation [2 points]

We've so far just used vector concatenation to represent the premise and hypothesis words. This question asks you to explore two simple alternative:

1. Write a function `vec_diff` that, for a given pair of vector inputs `u` and `v`, returns the element-wise difference between `u` and `v`.

1. Write a function `vec_max` that, for a given pair of vector inputs `u` and `v`, returns the element-wise max values between `u` and `v`.

You needn't include your uses of `nli.wordentail_experiment` with these functions, but we assume you'll be curious to see how they do!

```python
def vec_diff(u, v):
    pass
    ##### YOUR CODE HERE
    return u - v

def vec_max(u, v):
    pass
    ##### YOUR CODE HERE
    return np.maximum(u, v)

u = np.array([10.2, 8.1])
v = np.array([1.2, -7.1])
vec_max(u,v)
```

    array([10.2,  8.1])

```python
def test_vec_diff(vec_diff):
    u = np.array([10.2, 8.1])
    v = np.array([1.2, -7.1])
    result = vec_diff(u, v)
    expected = np.array([9.0, 15.2])
    assert np.array_equal(result, expected), \
        "Expected {}; got {}".format(expected, result)
```

```python
test_vec_diff(vec_diff)
```

```python
def test_vec_max(vec_max):
    u = np.array([1.2,  8.1])
    v = np.array([10.2, -7.1])
    result = vec_max(u, v)
    expected = np.array([10.2, 8.1])
    assert np.array_equal(result, expected), \
        "Expected {}; got {}".format(expected, result)
```

```python
test_vec_max(vec_max)
```

### A deeper network [2 points]

It is very easy to subclass `TorchShallowNeuralClassifier` if all you want to do is change the network graph: all you have to do is write a new `build_graph`. If your graph has new arguments that the user might want to set, then you should also redefine `__init__` so that these values are accepted and set as attributes.

For this question, please subclass `TorchShallowNeuralClassifier` so that it defines the following graph:

$$\begin{align}
h_{1} &= xW_{1} + b_{1} \\
r_{1} &= \textbf{Bernoulli}(1 - \textbf{dropout_prob}, n) \\
d_{1} &= r_1 * h_{1} \\
h_{2} &= f(d_{1}) \\
h_{3} &= h_{2}W_{2} + b_{2}
\end{align}$$

Here, $r_{1}$ and $d_{1}$ define a dropout layer: $r_{1}$ is a random binary vector of dimension $n$, where the probability of a value being $1$ is given by $1 - \textbf{dropout_prob}$. $r_{1}$ is multiplied element-wise by our first hidden representation, thereby zeroing out some of the values. The result is fed to the user's activation function $f$, and the result of that is fed through another linear layer to produce $h_{3}$. (Inside `TorchShallowNeuralClassifier`, $h_{3}$ is the basis for a softmax classifier; no activation function is applied to it because the softmax scaling is handled internally by the loss function.)

For your implementation, please use `nn.Sequential`, `nn.Linear`, and `nn.Dropout` to define the required layers.

For comparison, using this notation, `TorchShallowNeuralClassifier` defines the following graph:

$$\begin{align}
h_{1} &= xW_{1} + b_{1} \\
h_{2} &= f(h_{1}) \\
h_{3} &= h_{2}W_{2} + b_{2}
\end{align}$$

The following code starts this sub-class for you, so that you can concentrate on `build_graph`. Be sure to make use of `self.dropout_prob`.

For this problem, submit just your completed  `TorchDeepNeuralClassifier`. You needn't evaluate it, though we assume you will be keen to do that!

You can use `test_TorchDeepNeuralClassifier` to ensure that your network has the intended structure.

```python
import torch.nn as nn

class TorchDeepNeuralClassifier(TorchShallowNeuralClassifier):
    def __init__(self, dropout_prob=0.7, **kwargs):
        self.dropout_prob = dropout_prob
        super().__init__(**kwargs)

    def build_graph(self):
        """Complete this method!

        Returns
        -------
        an `nn.Module` instance, which can be a free-standing class you
        write yourself, as in `torch_rnn_classifier`, or the outpiut of
        `nn.Sequential`, as in `torch_shallow_neural_classifier`.

        """
        pass
        ##### YOUR CODE HERE
        dropout_layer = nn.Dropout(p=self.dropout_prob)

        return nn.Sequential(
            nn.Linear(self.input_dim, self.hidden_dim),
            dropout_layer,
            self.hidden_activation,
            nn.Linear(self.hidden_dim, self.n_classes_))

```

```python
def test_TorchDeepNeuralClassifier(TorchDeepNeuralClassifier):
    dropout_prob = 0.55
    assert hasattr(TorchDeepNeuralClassifier(), "dropout_prob"), \
        "TorchDeepNeuralClassifier must have an attribute `dropout_prob`."
    try:
        inst = TorchDeepNeuralClassifier(dropout_prob=dropout_prob)
    except TypeError:
        raise TypeError("TorchDeepNeuralClassifier must allow the user "
                        "to set `dropout_prob` on initialization")
    inst.input_dim = 10
    inst.n_classes_ = 5
    graph = inst.build_graph()
    assert len(graph) == 4, \
        "The graph should have 4 layers; yours has {}".format(len(graph))
    expected = {
        0: 'Linear',
        1: 'Dropout',
        2: 'Tanh',
        3: 'Linear'}
    for i, label in expected.items():
        name = graph[i].__class__.__name__
        assert label in name, \
            ("The {} layer of the graph should be a {} layer; "
            "yours is {}".format(i, label, name))
    assert graph[1].p == dropout_prob, \
        ("The user's value for `dropout_prob` should be the value of "
         "`p` for the Dropout layer.")
```

```python
test_TorchDeepNeuralClassifier(TorchDeepNeuralClassifier)
```

### Your original system [3 points]

This is a simple dataset, but its "word-disjoint" nature ensures that it's a challenging one, and there are lots of modeling strategies one might adopt.

You are free to do whatever you like. We require only that your system differ in some way from those defined in the preceding questions. They don't have to be completely different, though. For example, you might want to stick with the model but represent examples differently, or the reverse.

You are free to use different pretrained word vectors and the like.

Please embed your code in this notebook so that we can rerun it.

In the cell below, please provide a brief technical description of your original system, so that the teaching team can gain an understanding of what it does. This will help us to understand your code and analyze all the submissions to identify patterns and strategies.  We also ask that you report the best score your system got during development, just to help us understand how systems performed overall.

```python
from torch_rnn_classifier import TorchRNNClassifier, TorchRNNModel
import torch
from sklearn.metrics import classification_report, accuracy_score, f1_score
import time

class TorchRNNSentenceEncoderDataset(torch.utils.data.Dataset):
    def __init__(self, prem_seqs, hyp_seqs, prem_lengths, hyp_lengths, y=None):
        self.prem_seqs = prem_seqs
        self.hyp_seqs = hyp_seqs
        self.prem_lengths = prem_lengths
        self.hyp_lengths = hyp_lengths
        self.y = y
        assert len(self.prem_seqs) == len(self.hyp_seqs)
        assert len(self.hyp_seqs) == len(self.prem_lengths)
        assert len(self.prem_lengths) == len(self.hyp_lengths)
        if self.y is not None:
            assert len(self.hyp_lengths) == len(self.y)

    @staticmethod
    def collate_fn(batch):
        batch = list(zip(*batch))
        X_prem = torch.nn.utils.rnn.pad_sequence(batch[0], batch_first=True)
        X_hyp = torch.nn.utils.rnn.pad_sequence(batch[1], batch_first=True)
        prem_lengths = torch.tensor(batch[2])
        hyp_lengths = torch.tensor(batch[3])
        if len(batch) == 5:
            y = torch.tensor(batch[4])
            return X_prem, X_hyp, prem_lengths, hyp_lengths, y
        else:
            return X_prem, X_hyp, prem_lengths, hyp_lengths

    def __len__(self):
        return len(self.prem_seqs)

    def __getitem__(self, idx):
        if self.y is None:
            return (self.prem_seqs[idx], self.hyp_seqs[idx],
                    self.prem_lengths[idx], self.hyp_lengths[idx])
        else:
            return (self.prem_seqs[idx], self.hyp_seqs[idx],
                    self.prem_lengths[idx], self.hyp_lengths[idx],
                    self.y[idx])

class TorchRNNSentenceEncoderClassifierModel(nn.Module):
    def __init__(self, prem_rnn, hyp_rnn, output_dim):
        super().__init__()
        self.prem_rnn = prem_rnn
        self.hyp_rnn = hyp_rnn
        self.output_dim = output_dim
        self.bidirectional = self.prem_rnn.bidirectional
        # Doubled because we concatenate the final states of
        # the premise and hypothesis RNNs:
        self.classifier_dim = self.prem_rnn.hidden_dim * 2
        # Bidirectionality doubles it again:
        if self.bidirectional:
            self.classifier_dim *= 2
        self.classifier_layer = nn.Linear(
            self.classifier_dim, self.output_dim)

    def forward(self, X_prem, X_hyp, prem_lengths, hyp_lengths):
        # Premise:
        _, prem_state = self.prem_rnn(X_prem, prem_lengths)
        prem_state = self.get_batch_final_states(prem_state)
        # Hypothesis:
        _, hyp_state = self.hyp_rnn(X_hyp, hyp_lengths)
        hyp_state = self.get_batch_final_states(hyp_state)
        # Final combination:
        state = torch.cat((prem_state, hyp_state), dim=1)
        # Classifier layer:
        logits = self.classifier_layer(state)
        return logits

    def get_batch_final_states(self, state):
        if self.prem_rnn.rnn.__class__.__name__ == 'LSTM':
            state = state[0].squeeze(0)
        else:
            state = state.squeeze(0)
        if self.bidirectional:
            state = torch.cat((state[0], state[1]), dim=1)
        return state

class TorchRNNSentenceEncoderClassifier(TorchRNNClassifier):

    def build_dataset(self, X, y=None):
        X_prem, X_hyp = zip(*X)
        X_prem, prem_lengths = self._prepare_sequences(X_prem)
        X_hyp, hyp_lengths = self._prepare_sequences(X_hyp)
        if y is None:
            return TorchRNNSentenceEncoderDataset(
                X_prem, X_hyp, prem_lengths, hyp_lengths)
        else:
            self.classes_ = sorted(set(y))
            self.n_classes_ = len(self.classes_)
            class2index = dict(zip(self.classes_, range(self.n_classes_)))
            y = [class2index[label] for label in y]
            return TorchRNNSentenceEncoderDataset(
                X_prem, X_hyp, prem_lengths, hyp_lengths, y)

    def build_graph(self):
        prem_rnn = TorchRNNModel(
            vocab_size=len(self.vocab),
            embedding=self.embedding,
            use_embedding=self.use_embedding,
            embed_dim=self.embed_dim,
            rnn_cell_class=self.rnn_cell_class,
            hidden_dim=self.hidden_dim,
            bidirectional=self.bidirectional,
            freeze_embedding=self.freeze_embedding)

        model = TorchRNNSentenceEncoderClassifierModel(
            prem_rnn, prem_rnn, output_dim=self.n_classes_)

        self.embed_dim = prem_rnn.embed_dim

        return model

```

```python
start_token = '<s>'
end_token = '</s>'
sep_token = '<sep>'
# Load in GloVe embeddings
glove_lookup = utils.glove2dict(
    os.path.join(GLOVE_HOME, 'glove.6B.300d.txt'))
```

```python
## WordNet & GloVe analysis
from nltk.corpus import wordnet as wn
print(f"Vocabulary size: {len(wordentail_data['vocab'])}")
ctr = 0
g_ctr = 0
n_ctr = 0
for word in wordentail_data['vocab']:
  syn = wn.synsets(word)
  if len(syn) == 0:
    ctr +=1
  gv = glove_lookup.get(word)
  if gv is None:
    g_ctr += 1
  if len(syn) == 0 and gv is None:
    n_ctr += 1

print(f"Words not in WordNet {ctr}")
print(f"Words not in GloVe {g_ctr}")
print(f"Words in neither WordNet nor GloVe {n_ctr}")

syn = wn.synsets("apple")
print(syn[0].lemma_names())
print(syn[1].lemma_names())

syn = wn.synsets("carrot")
print(syn)
print(f"{syn[0].lemma_names()} ... {syn[0].definition()}")
print(f"{syn[1].lemma_names()} ... {syn[1].definition()}")
print(f"{syn[2].lemma_names()} ... {syn[2].definition()}")
print(f"{syn[3].lemma_names()} ... {syn[3].definition()}")

```

    Vocabulary size: 8470
    Words not in WordNet 577
    Words not in GloVe 391
    Words in neither WordNet nor GloVe 198
    ['apple']
    ['apple', 'orchard_apple_tree', 'Malus_pumila']
    [Synset('carrot.n.01'), Synset('carrot.n.02'), Synset('carrot.n.03'), Synset('carrot.n.04')]
    ['carrot'] ... deep orange edible root of the cultivated carrot plant
    ['carrot', 'cultivated_carrot', 'Daucus_carota_sativa'] ... perennial plant widely cultivated as an annual in many varieties for its long conical orange edible roots; temperate and tropical regions
    ['carrot'] ... orange root; important source of carotene
    ['carrot'] ... promise of reward as in

```python
def get_word_definitions(w):
  syns = wn.synsets(w)
  defn = start_token + ' ' + w + ' '
  #defn = w + ' '
  for s in syns:
    defn += s.definition()
    defn += ' ' + sep_token + ' '
  defn += ' ' + end_token
  return defn.split()

def get_word_synonyms(w):
  syns = wn.synsets(w)
  st = {w}
  for s in syns:
    st.update(s.lemma_names())
  return list(st)

def get_entailment_sentence(wordentail):
  defn1 = get_word_definitions(wordentail[0][0])
  defn2 = get_word_definitions(wordentail[0][1])
  #defn1 = get_word_synonyms(wordentail[0][0])
  #defn2 = get_word_synonyms(wordentail[0][1])
  return (defn1, defn2)

def fit_simple_sentence_encoding_rnn_with_hyperparameter_search(X, y):
  vocab = [start_token, sep_token, end_token]
  vocab += wordentail_data['vocab']
  glove_embedding, nli_glove_vocab = utils.create_pretrained_embedding(
      glove_lookup, vocab)

  model = TorchRNNSentenceEncoderClassifier(
      nli_glove_vocab,
      embedding=glove_embedding,
      bidirectional=True,
      hidden_dim=100,
      early_stopping=True,
      max_iter=3)

  param_grid = {
      'batch_size': [32, 64, 128, 256],
      'eta': [0.0001, 0.001, 0.01, 0.1]}
#   param_grid = {
#       'batch_size': [64],
#       'eta': [0.01]}

  bestmod = utils.fit_classifier_with_hyperparameter_search(
      X, y, model, cv=3, param_grid=param_grid)

  return bestmod

def simple_example():
    print("Running a simple example")
    start = time.time()

    train = [[get_entailment_sentence(wordentail), wordentail[1]] for wordentail in wordentail_data['train'][:1000]]
    test = [[get_entailment_sentence(wordentail), wordentail[1]] for wordentail in wordentail_data['train'][10:100]]

    X_train, y_train = zip(*train)
    X_dev, y_dev = zip(*test)

    print("Searching for best hyperparameters")
    model = fit_simple_sentence_encoding_rnn_with_hyperparameter_search(X_train, y_train)
    model.max_iter = 1000

    model.fit(X_train, y_train)

    predictions = model.predict(X_dev)

    # Report:
    print(classification_report(y_dev, predictions, digits=3))
    macrof1 = utils.safe_macro_f1(y_dev, predictions)
    print(time.time() - start)

#simple_example()

```

```python
# PLEASE MAKE SURE TO INCLUDE THE FOLLOWING BETWEEN THE START AND STOP COMMENTS:
#   1) Textual description of your system.
#   2) The code for your original system.
#   3) The score achieved by your system in place of MY_NUMBER.
#        With no other changes to that line.
#        You should report your score as a decimal value <=1.0
# PLEASE MAKE SURE NOT TO DELETE OR EDIT THE START AND STOP COMMENTS

# IMPORT ANY MODULES BELOW THE 'IS_GRADESCOPE_ENV' CHECK CONDITION. DOING
# SO ABOVE THE CHECK MAY CAUSE THE AUTOGRADER TO FAIL.

# START COMMENT: Enter your system description in this cell.
# My peak score was: MY_NUMBER
if 'IS_GRADESCOPE_ENV' not in os.environ:
  pass
  start_time = time.time()

  train = [[get_entailment_sentence(wordentail), wordentail[1]] for wordentail in wordentail_data['train']]
  test = [[get_entailment_sentence(wordentail), wordentail[1]] for wordentail in wordentail_data['dev']]

  X_train, y_train = zip(*train)

  print("Searching for best hyperparameters")
  model = fit_simple_sentence_encoding_rnn_with_hyperparameter_search(X_train, y_train)
  model.max_iter = 1000

  print("Fitting the full model")
  model.fit(X_train, y_train)

  X_dev, y_dev = zip(*test)
  predictions = model.predict(X_dev)

  # Report:
  rep = classification_report(y_dev, predictions, digits=3)
  print(rep)
  macrof1 = utils.safe_macro_f1(y_dev, predictions)
  res = {
      'macro-F1': macrof1,
      'model': model,
      'train_data': wordentail_data['train'],
      'assess_data': wordentail_data['dev'],
      }

  with open('hw_wordentail_orig_sys.txt', 'w') as f:
    print(f'Took {time.time() - start_time} seconds', file=f)
    print(f'Classification report: {rep}', file=f)
    print(f'Result dictionary: {res}', file=f)

# STOP COMMENT: Please do not remove this comment.
```

    Searching for best hyperparameters

    Finished epoch 3 of 3; error is 36.265901450067768

    Best params: {'batch_size': 32, 'eta': 0.001}
    Best score: 0.767
    Fitting the full model

    Stopping after epoch 18. Validation score did not improve by tol=1e-05 for more than 10 epochs. Final error is 10.007551011745818

                  precision    recall  f1-score   support

               0      0.878     0.849     0.863      1732
               1      0.330     0.386     0.356       334

        accuracy                          0.774      2066
       macro avg      0.604     0.617     0.609      2066
    weighted avg      0.789     0.774     0.781      2066

## Bake-off [1 point]

The goal of the bake-off is to achieve the highest __macro-average F1__ score on a test set that we will make available at the start of the bake-off. The announcement will go out on the discussion forum. To enter, you'll be asked to run `nli.bake_off_evaluation` on the output of your chosen `nli.wordentail_experiment` run.

The cells below this one constitute your bake-off entry.

The rules described in the [Your original system](#Your-original-system-[3-points]) homework question are also in effect for the bake-off.

Systems that enter will receive the additional homework point, and systems that achieve the top score will receive an additional 0.5 points. We will test the top-performing systems ourselves, and only systems for which we can reproduce the reported results will win the extra 0.5 points.

Late entries will be accepted, but they cannot earn the extra 0.5 points. Similarly, you cannot win the bake-off unless your homework is submitted on time.

The announcement will include the details on where to submit your entry.

```python
# Enter your bake-off assessment code into this cell.
# Please do not remove this comment.
if 'IS_GRADESCOPE_ENV' not in os.environ:
    pass
    # Please enter your code in the scope of the above conditional.
    ##### YOUR CODE HERE

```

```python
# On an otherwise blank line in this cell, please enter
# your macro-avg f1 value as reported by the code above.
# Please enter only a number between 0 and 1 inclusive.
# Please do not remove this comment.
if 'IS_GRADESCOPE_ENV' not in os.environ:
    pass
    # Please enter your score in the scope of the above conditional.
    ##### YOUR CODE HERE

```


# Objective

In this lab, we will investigate the effects of temperature on electronic devices -- resistors that change value with temperature and thermally-generated free charge carriers on the reverse-bias current through a diode.

**NOTE:** Do not skim-read the procedure.  The wording is precise and specific, skipping steps or missing things will *guarantee* you will not finish your measurements within the scheduled lab time.  Many problems will be avoided if you do not try to rush through the procedure.

**READ THE PROCEDURE CAREFULLY**

**READ THE PROCEDURE CAREFULLY**

**slow down...!**

# Theory

Skip this section for now and start with the Procedure section, read this while waiting for your measurements to stabilize.

## Silicon *pn* diode

The silicon *pn* junction diode has an ideal current-voltage relationship of:

\begin{equation}
I_D = I_s \left(e^{V_D \cdot q / (n k_B T)} - 1\right)
\end{equation}

The term $k_B T /q$ is known as the *thermal voltage* $V_T$ and is approximately $26 \mathrm{mV}$ at room temperature, $n$ is called the *ideality factor* and is 1 for an ideal diode but larger (1--2 range) for a real diode.  $I_S$ is called the *saturation current*.  For reverse-bias (negative) voltages larger than a few hundred $\mathrm{mV}$, the diode current can be considered $\approx -I_S$, to a first-approximation.

Besides the $1/T$ term explicitly showing up in the equation's exponent, the saturation current is a strong function of temperature as well.  It can be calculated from geometrical and material parameters as:

\begin{equation}
I_S = A q n_i^2 \left(\dfrac{D_n}{N_A L_n} + \dfrac{D_p}{N_D L_p}\right)
\end{equation}

where $A$ is the device's cross-sectional area, $L_n$ and $L_p$ are electron and hole "diffusion lengths," respectively, $D_{n,p}$ are the diffusion constants, and $N_{A,D}$ are the doping for the *p* and *n* sides.  These parameters can be considered constants here.

The Einstein Relation can be proven to relate the ratio of the diffusion constant to mobility in a material as:

\begin{equation}
\dfrac{D_{n,p}}{\mu_{n,p}} = \dfrac{k_B T}{q} = V_T
\end{equation}

Solving for the diffusion constant, $D_{n,p} = \mu_{n,p} \dfrac{k_B T}{q}$, re-write the saturation current and factor out temperature:

\begin{equation}
I_S = A q n_i^2 T \left(\dfrac{\mu_n k_B}{q N_A L_n} + \dfrac{\mu_p k_B}{q N_D L_p}\right)
\end{equation}

Ignore the fact that mobility changes with temperature due to several effects, reaching a maximum at an intermediate temperature.

The intrinsic carrier density also varies with temperature:

\begin{equation}
n_i^2 = B T^3 e^{-E_G/(n k_b T)}
\end{equation}

so,

\begin{equation}
I_S = A q B T^4 e^{-E_G/(n k_b T)} \left(\dfrac{\mu_n k_B}{q N_A L_n} + \dfrac{\mu_p k_B}{q N_D L_p}\right)
\end{equation}

We are interested in the temperature dependence of $I_S$ for a specific device, so collapse all the temperature-independent terms into a single constant:

\begin{equation}
I_S(T) = C \cdot T^4 e^{-E_G/(n k_b T)}
\end{equation}

## Temperature-dependent resistors

All materials change resistivity with changes in temperature.  The different types of resistor materials have varying sensitivity to temperature.  This change in resistance versus temperature for "normal" resistors is specified as its temperature coefficient of resistance (TCR) and has units of percent or PPM per degree.  Usually, $T_{ref}$ is room temperature.

\begin{equation}
R(T) = R_{T_{ref}}\left[ 1 + TCR\left(\frac{T}{T_{ref}}\right) \right]
\end{equation}

See (Temperature Coefficient of Copper)[http://www.cirris.com/learning-center/general-testing/special-topics/177-temperature-coefficient-of-copper] for more information.

Thermistors are devices which are *intended to* change their resistance with temperature.  Their obvious application is to measure temperature.  Other interesting applications include measurement of air speed and density in aircraft as the airstream cools the device.  Their resistance is dependent on temperature as approximately:

\begin{equation}
R(T) = R_R \exp \left[B\left(\dfrac{1}{T} - \dfrac{1}{T_R}\right)\right]
\end{equation}

where $B$ is a constant for the specific device, and $R_R$ and $T_R$ are the resistance at the rated temperature.  For the thermistors used in the lab, they are rated $1\mathrm{k\Omega}$ at $25^\circ C$ ($R_R=1k$, $T_R=25^\circ C$) with constant $B = 3930 K$.  Be mindful of the temperature units in your calculations ($0^\circ C =  273.15 K$).  See the file `NTC-general-technical-information.pdf` on Blackboard for more background information and `NTC_Leaded_disks_M891.pdf` for the specific values.

A more accurate determination of resistance or temperature is obtained from using actual values and temperatures provided in tables in the device's datasheet and interpolating between the values.  Page 2 of the `NTC-standardizedrt.pdf` file on Blackboard describes the calculations and gives an example.  We are using the $1 \mathrm{k\Omega}$ rated thermistor, whose data table is number "`1009`" in the datasheet, file `NTC_Leaded_disks_M891.pdf` on Blackboard.

# Procedure

The general format of these instructions is: the first 1-2 sentences are your task to do and the rest of the item is a short description of what the task is about.

**Goal:** Setup your oscilloscope to precisely measure very small DC values in the presence of noise and interference.

1. Turn down both outputs of the benchtop power supply to zero before turning it on.

1. Connect the $+6\,\mathrm{V}$ power supply terminal to the node labeled `6V_or_0-20V` in **Figure 1**.

1. Connect the $+20\,\mathrm{V}$ power supply terminal to the node labeled `0-20V` in **Figure 1**.

1. Connect the `COM` terminal of the power supply to the node labeled `GND` in **Figure 1**.

1. Connect oscilloscope channel 1's "+" terminal (the tip) to the triangle side of the diode and the black ground clip lead to the node labeled `GND`.

   * This makes it appear that channel 1 is connected across *nothing*, which is not necessarily intuitive.  The currents we will be measuring will start at a few $\mathrm{nA}$, a million times below the range of cheap ammeters (the nice multimeters can measure this, but we don't have enough of them for every lab station).  With this connection scheme, the saturation current $I_S$ is forced to go through the $10 \,\mathrm{M\Omega}$ input resistance of the oscilloscope+probe combination.  It is operating as a *current shunt*.  The voltage measured across this resistance by the scope is proportional to the current through it via Ohms "Law".  This current $I_S$ is shown in **Figure 1**.

1. Connect oscilloscope channel 2's "+" terminal to the node between the MiniChamber's thermistor and the $2.7 \,\mathrm{k\Omega}$ resistor.

1. Turn on the power supply.  The outputs should already have been both set to zero.

1. Raise the $+6\,\mathrm{V}$ power supply output to about $+6\,\mathrm{V}$ and record the actual value shown on the power supply's meter.  It is not critical to be at exactly $6.00$, but it **is** important to use the true measured value for your data collection and calculations.

1. Touch the "Default Setup" key on the scope then select the "Factory Default" option in the soft menu (confirm that you want to reset to factory settings).  This completely resets your scope's settings.  Default Setup alone still leaves some settings unchanged.

1. Touch the "Acquire" key on the scope and change the Acquire Mode to "High Res".  The manual says the scope displays the average of the many samples taken by the front-end for each displayed pixel in this mode.  "Normal" mode merely displays the first sample value in the interval.

1. Change the trigger system to **trigger** on the **edge** of the **"Line" source**.  The signal you see is the superposition of the DC value to be measured, noise, and the $60 \,\mathrm{Hz}$ electric and magnetic fields in the room from the building power.  The signals we are interested in are DC only, but the probe will pick up a large amount of powerline interference as you can see on the screen.  This keeps the display stable so we can then average-out the display and make more precise measurements.

1. Change the vertical scale and vertical position of both channels ($V_{thm}$ and $V_{I_S}$) to show both the entire signal waveform and the channel's $0 \,\mathrm{V}$ reference level.  The goal is to maximize the amount of screen area the signal takes up while still keeping the 0 reference and signal on the screen without spilling above or below.  This gives you the most accurate measurements.

1. Change the horizontal scale to show at least 5 periods of the $60 \,\mathrm{Hz}$ interference waveform.

1. Using the measurement functions, display the *full-screen averages* of both channel voltages.

1. Observe these measurements and record them when they stop slowly changing up or down.

1. Increase the $0-20 \,\mathrm{V}$ power supply output in $2.0 \,\mathrm{V}$ increments until it reaches $6.0 \,\mathrm{V}$ and then $1.0 \,\mathrm{V}$ increments.  (0, 2, 4, 6, **7**, **8**, **9**, ...)

      * At each increment, wait for the temperature to reach equilibrium, indicated when both measurements stop changing.  Calculate a time to wait between making a change and taking each measurement that allows you to finish all your measurements at 10 minutes before the lab session ends.

      * These changes will be slow and small at first.  $V_{I_S}$ will be around $40 \,\mathrm{mV}$ at the beginning.  **(You)** Calculate the expected value of $V_{thm}$ at room temperature, when the thermistor resistance is around $1 \,\mathrm{k\Omega}$.

      * At the end of each increment's time interval:
        * Record **5** data values:
        * The $20 \mathrm{V}$ supply voltage
        * The $20 \mathrm{V}$ supply current
        * The voltage at the node labeled `6V_or_0-20V`.
        * $V_{I_S}$ (oscilloscope channel 1)
        * $V_{thm}$ (oscilloscope channel 2)

      * When the $20 \,\mathrm{V}$ supply increment reaches $6 \,\mathrm{V}$: Move the power supply connection to node labeled `6V_or_0-20V` from the $6\,\mathrm{V}$ output to the $20 \,\mathrm{V}$ output.  The diode, heater, and $2.7 \,\mathrm{k\Omega}$ resistor will therefore all be connected to the $20 \,\mathrm{V}$ output and the $6 \mathrm{V}$ power supply will then be disconnected.

      * Stop the experiment when either the thermistor reaches the calculated resistance for $100 \,\mathrm{^\circ C}$ **or** the MiniChamber appears to be becoming damaged (smoke, noise, etc.).

1. While waiting for each measurement to stabilize: setup your calculations and plots, viewing each new data point as you enter them.  It is useful to pre-define the "plot range" to be the full number of rows you expect to have, this way you do not need to update that range for each new data point.

1. Calculate what the thermistor's resistance will be at $100 \mathrm{^\circ C}$ using **Equation 9**.  This is the maximum temperature the MiniChamber should reach with the corresponding thermistor resistance.
\newpage
## Analysis

Use Excel or Google Sheets to perform these calculations.

1. Using circuit analysis and the thermistor datasheet, determine the **thermistor resistance** and **temperature** for each of your $V_{thm}$ measurements.

2. Calculate the **heat power** supplied to the system by the heater resistor at each measurement point.

3. Using circuit analysis, calculate the **diode saturation current $I_S$** as measured by the oscilloscope probe $V_{Is}$.  (Technically we are measuring the diode's leakage current instead.)

4. From the heater resistor's applied voltage and resulting measured current, calculate the **heater's effective resistance**.  It changes with temperature :(

At this point, your spreadsheet will have 5 columns of raw data, and 5 columns of calculated values from this data.  Keep the columns in separate groups to visually differentiate from raw data measurements and derived numbers; this is good lab practice.

From the numbers, generate the following plots:
(remember: "$y$ vs. $x$")

1. $I_S$ vs. temperature

    * On this figure, also plot the theoretical value of $I_S$ versus temperature.  Choose the constants $C$ and $n$ which best fit your measured data.  $n$ will be between $1$ and $2$.
    * Use a log scale for $I_S$ and linear scale for temperature.
    * Compare the measured and theoretical plots and comment on them.
    * Compare this plot to Figure 5 of the `1N4148_NXP.pdf` datasheet.

2. Temperature vs. heat power

    * Also calculate the slope in units of $^\circ \mathrm{C} / W$.
    * This represents the thermal resistance of the heat transfer from the heater resistor to the room of the entire device.  ECEs use this type of information in calculating the size of heatsinks.

3. Heater resistance vs. temperature

    * Find the slope as $\mathrm{PPM} / ^\circ \mathrm{C}$.   Parts-per-million (PPM) is like percentage, but uses $1 / 10^6$ instead of the $1 / 100$ factor for percentage.  PPM is a very common term for specifying small relative measurements.

## Report

Refer to the document `Lab Report Guidelines.pdf` on Blackboard for the format for your report.


\title{Digital Latches with myHDL}
\author{Steven K Armour}
\maketitle

# Refs
@book{brown_vranesic_2014, place={New York, NY}, edition={3}, title={Fundamentals of digital logic with Verilog design}, publisher={McGraw-Hill}, author={Brown, Stephen and Vranesic, Zvonko G}, year={2014} },
@book{lameres_2017, title={Introduction to logic circuits & logic design with Verilog}, publisher={springer}, author={LaMeres, Brock J}, year={2017} }

# Acknowledgments

Author of **myHDL** [Jan Decaluwe](http://www.myhdl.org/users/jandecaluwe.html) and the author of the **myHDL Peeker** [XESS Corp.](https://github.com/xesscorp/myhdlpeek)

[**Draw.io**](https://www.draw.io/)

**Xilinx**

# Python Libraries Utilized

```python
import numpy as np
import pandas as pd
from sympy import *
init_printing()

from myhdl import *
from myhdlpeek import *
import random

#python file of convince tools. Should be located with this notebook
from sympy_myhdl_tools import *
```

# Latches vs Flip-Flops

Latches and Flip-Flops are both metastaple logic circuit tobologies in that once loaded with a state they hold that state information till that state is upset by a new state or a reset command. But the diffrance between the two is that Flip-Flops are clock controlled devices built upon Latches where as Latches are not clock dependent

# SR-Latch

## Symbol and Internals
The Symbol for a SR-Latch and one representation of it's internals is shown below

## Definition

## State Diagram

## myHDL SR-Latch Gate and Testing

Need Help Getting this Latch via Combo Cirucits working geting  AlwayCombError in using out signal as argument in out signals next state out
def LatchCombo(S_in, rst, Q_out):

    @always_comb
    def logic():
        Q_out.next=(not rst) and (S_in or Q_out)
    return logicPeeker.clear()
S_in, rst, Q_out=[Signal(bool(0)) for _ in range(3)]
Peeker(S_in, 'S_in'); Peeker(rst, 'rst')
Peeker(Q_out, 'Q_out')

DUT=LatchCombo(S_in=S_in, rst=rst, Q_out=Q_out)

inputs=[S_in, rst]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='AND 2 gate simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```python

```

## myHDL SR-Latch Behavioral and Testing

```python
def SRLatch(S_in, rst, Q_out, Qn_out):
    @always_comb
    def logic():
        if S_in and rst==0:
            Q_out.next=1
            Qn_out.next=0

        elif S_in==0 and rst:
            Q_out.next=0
            Qn_out.next=1

        elif S_in and rst:
            Q_out.next=0
            Qn_out.next=0

    return logic
```

```python
S_in, rst, Q_out, Qn_out=[Signal(bool(0)) for _ in range(4)]
Peeker.clear()

Peeker(S_in, 'S_in'); Peeker(rst, 'rst')
Peeker(Q_out, 'Q_out'); Peeker(Qn_out, 'Qn_out')

DUT=SRLatch(S_in=S_in, rst=rst, Q_out=Q_out, Qn_out=Qn_out)
inputs=[S_in, rst]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='SRLatch Behavioral simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
```

      Q_out
      Qn_out
      S_in
      rst

      0
      0
      0
      0
      0

      1
      0
      1
      0
      1

      2
      1
      0
      1
      0

      3
      0
      0
      1
      1

## myHDL SR-Latch Behavioral HDL Synthesis

```python
toVerilog(SRLatch, S_in, rst, Q_out, Qn_out)
#toVHDL(SRLatch, S_in, rst, Q_out, Qn_out)
_=VerilogTextReader('SRLatch')
```

    ***Verilog modual from SRLatch.v***

     // File: SRLatch.v
    // Generated by MyHDL 0.9.0
    // Date: Thu Oct 26 00:40:43 2017

    `timescale 1ns/10ps

    module SRLatch (
        S_in,
        rst,
        Q_out,
        Qn_out
    );

    input S_in;
    input rst;
    output Q_out;
    reg Q_out;
    output Qn_out;
    reg Qn_out;

    always @(rst, S_in) begin: SRLATCH_LOGIC
        if ((S_in && (rst == 0))) begin
            Q_out = 1;
            Qn_out = 0;
        end
        else if (((S_in == 0) && rst)) begin
            Q_out = 0;
            Qn_out = 1;
        end
        else if ((S_in && rst)) begin
            Q_out = 0;
            Qn_out = 0;
        end
    end

    endmodule

The following shows the **Xilinx**'s _Vivado 2016.1_ RTL generated schematic of our Behaviorla SRLatch from the synthesised verilog code. We can see that the systhizied version is quite apstract from fig lakdfjkaj.

# Gated SR-Latch

## myHDL SR-Latch Behavioral and Testing

```python
def GSRLatch(S_in, rst, ena, Q_out, Qn_out):
    @always_comb
    def logic():
        if ena:
            if S_in and rst==0:
                Q_out.next=1
                Qn_out.next=0

            elif S_in==0 and rst:
                Q_out.next=0
                Qn_out.next=1

            elif S_in and rst:
                Q_out.next=0
                Qn_out.next=0
        else:
            pass

    return logic
```

```python
S_in, rst, ena, Q_out, Qn_out=[Signal(bool(0)) for _ in range(5)]
Peeker.clear()

Peeker(S_in, 'S_in'); Peeker(rst, 'rst'); Peeker(ena, 'ena')
Peeker(Q_out, 'Q_out'); Peeker(Qn_out, 'Qn_out')

DUT=GSRLatch(S_in=S_in, rst=rst, ena=ena, Q_out=Q_out, Qn_out=Qn_out)
inputs=[S_in, rst, ena]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='GSRLatch Behavioral simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
```

      Q_out
      Qn_out
      S_in
      ena
      rst

      0
      0
      0
      0
      0
      0

      1
      0
      0
      0
      1
      0

      2
      0
      0
      0
      0
      1

      3
      0
      1
      0
      1
      1

      4
      0
      1
      1
      0
      0

      5
      1
      0
      1
      1
      0

      6
      1
      0
      1
      0
      1

      7
      0
      0
      1
      1
      1

## myHDL SR-Latch Behavioral HDL Synthesis

```python
toVerilog(GSRLatch, S_in, rst, ena, Q_out, Qn_out)
#toVHDL(GSRLatch, S_in, rst,ena, Q_out, Qn_out)
_=VerilogTextReader('GSRLatch')
```

    ***Verilog modual from GSRLatch.v***

     // File: GSRLatch.v
    // Generated by MyHDL 0.9.0
    // Date: Thu Oct 26 00:40:44 2017

    `timescale 1ns/10ps

    module GSRLatch (
        S_in,
        rst,
        ena,
        Q_out,
        Qn_out
    );

    input S_in;
    input rst;
    input ena;
    output Q_out;
    reg Q_out;
    output Qn_out;
    reg Qn_out;

    always @(ena, S_in, rst) begin: GSRLATCH_LOGIC
        if (ena) begin
            if ((S_in && (rst == 0))) begin
                Q_out = 1;
                Qn_out = 0;
            end
            else if (((S_in == 0) && rst)) begin
                Q_out = 0;
                Qn_out = 1;
            end
            else if ((S_in && rst)) begin
                Q_out = 0;
                Qn_out = 0;
            end
        end
        else begin
            // pass
        end
    end

    endmodule

The following shows the **Xilinx**'s _Vivado 2016.1_ RTL generated schematic of our Behaviorla Gated SRLatch from the synthesised verilog code. We can see that the systhizied version is quite apstract from fig lakdfjkaj.

# D-Latch

## myHDL Behavioral D-Latch and Testing

```python
def DLatch(D_in, ena, Q_out, Qn_out):
    #Normal Qn_out is not specifed since a not gate is so easily implimented
    @always_comb
    def logic():
        if ena:
            Q_out.next=D_in
            Qn_out.next=not D_in
    return logic
```

```python
D_in, ena, Q_out, Qn_out=[Signal(bool(0)) for _ in range(4)]

Peeker.clear()

Peeker(D_in, 'D_in'); Peeker(ena, 'ena')
Peeker(Q_out, 'Q_out'); Peeker(Qn_out, 'Qn_out')

DUT=DLatch(D_in=D_in, ena=ena, Q_out=Q_out, Qn_out=Qn_out)
inputs=[D_in, ena]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
                  title='DLatch Behavioral simulation',
                  caption=f'after clock cycle {2**len(inputs)-1} ->random input')

```

    <class 'myhdl.StopSimulation'>: No more events

```python
MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
```

      D_in
      Q_out
      Qn_out
      ena

      0
      0
      0
      0
      0

      1
      0
      0
      1
      1

      2
      1
      0
      1
      0

      3
      1
      1
      0
      1

## myHDL DLatch Behavioral HDL Synthesis

```python
toVerilog(DLatch, D_in, ena, Q_out, Qn_out)
#toVHDL(DLatch,D_in, ena, Q_out, Qn_out)
_=VerilogTextReader('DLatch')
```

    ***Verilog modual from DLatch.v***

     // File: DLatch.v
    // Generated by MyHDL 0.9.0
    // Date: Thu Oct 26 00:40:46 2017

    `timescale 1ns/10ps

    module DLatch (
        D_in,
        ena,
        Q_out,
        Qn_out
    );

    input D_in;
    input ena;
    output Q_out;
    reg Q_out;
    output Qn_out;
    reg Qn_out;

    always @(ena, D_in) begin: DLATCH_LOGIC
        if (ena) begin
            Q_out = D_in;
            Qn_out = (!D_in);
        end
    end

    endmodule

The following shows the **Xilinx**'s _Vivado 2016.1_ RTL generated schematic of our myHDL Dlatch with a exsplisit $\bar{Q}$ verilog code. Note that becouse  $\bar{Q}$ is not normal declared in HDL code Vivado produced two RTL DLatchs and used a NOT Gate to acount for the negated output

# Examples


# **MAC023 - Mecânica das Estruturas**

# ME-03 - Terceira Avaliação de Conhecimentos

Alunos:

Brian Maia \\
Mathews Edwirds \\

# Condições Gerais

Esta avaliação tem como objetivo avaliar os conhecimentos adquiridos na primeira parte da disciplina de Mecânica das Estruturas.

---

As condicões abaixo devem ser observadas:

1. Serão formadas equipes e cada uma delas com no mínimo **2** e no máximo **3** integrantes.

2. A avaliação será realizada por meio da entrega de uma cópia deste notebook com as soluções desenvolvidas até a data estipulada de entrega.

3. Da entrega da avaliação.
  * Os documentos necessários para a entrega do trabalho são (1) os códigos  desenvolvidos pela equipe e (2) vídeo com a descrição da solução.
  * A equipe deve usar este modelo de notebook para desenvolver os códigos.
  * Os códigos podem ser desenvolvidos combinado a linguagem LaTeX e computação simbólica ou computação numérica quando necessário.
  * Os gráficos necessários para a apresentação da solução devem estar embutidos no notebook.

4. Da distribuição das questões.
  * A quantidade de questões será a mesma para cada grupo.
  * Serão atribuídas as mesmas questões para todos os grupos.
  * A pontuacão referente a cada questão será igualitária e o valor total da avaliação será 100 pontos.

5. As equipes devem ser formadas até às **23:59 horas o dia 22/02/2022** por meio do preenchimento da planilha [[MAC023] Formação das Equipes](https://docs.google.com/spreadsheets/d/1Dlftymao970nnrE4mu958iP8nMqKqSuhHiiLH91BKpQ/edit#gid=986079240).

6. A formação das equipes pode ser acompanhada arquivo indicado acima. Cada equipe será indentificada por uma letra em ordem alfabética seguida do número 3 (A3, B3, C3, e assim por diante). O arquivo está aberto para edição e pode ser alterado pelos alunos até a data estipulada.

7. Equipes formadas após a data estabelecida para a formação das equipes terão a nota da avaliação multiplicada por um coeficiente de **0.80**.

8. A equipe deve indicar no arquivo de indicação de equipes um responsável pela entrega do projeto.
  * Somente o responsável pela entrega deve fazer o upload do arquivo na plataforma

9. A entrega dos projetos deve ocorrer até às **23:59 do dia 25/02/2022** na plataforma da disciplina pelo responsável pela entrega.
  * Caso a entrega seja feita por outro integrante diferente daquele indicado pela pela equipe a avaliação será desconsiderada e não será corrigida até que a a condição de entrega seja satisfeita.

10. Quaisquer dúvidas ou esclarecimentos devem ser encaminhadas pela sala de aula virtual.

## (Q1) Implementação do método dos deslocamentos

Considere a estrutura abaixo de seção transversal retangular com 8 cm de base por 15 cm de altura. O material possui módulo de elasticidade de 70 GPa. Caso necessário, considere o coeficiente de Poisson igual a 0,30.

Proceda como se pede:

1. Enumere os graus de liberdade da estrutura
2. Determine os graus de liberdade associados as deslocabilidades
3. Determine a matriz de rigidez do membro AC
4. Determine a matriz de rigidez do membro CD
5. Determine a matriz de rigidez do membro DB
6. Determine a matriz de rigidez global da estrutura
7. Apresente os deslocamentos horizontal, vertical e a rotação do ponto C.

```
import numpy as np
import sympy as sp
sp.init_printing(use_latex=True)
```

# Leitura do arquivo de dados

```
# leitura do arquivo de entrada
from io import BytesIO
import pandas as pd
import requests

# acesso via link
link='https://docs.google.com/spreadsheet/ccc?key=1ZBDTypR4MOZqEiIXDqxt2JfW2Oy20SYoNsi2QHJjxKk&output=csv'

r = requests.get(link)
data = r.content

df = pd.read_csv(BytesIO(data), header=0) #, index_col=0
df
```

      MAC023 - Estrutura do trabalho 03
      Unnamed: 1
      Unnamed: 2
      Unnamed: 3
      Unnamed: 4
      Unnamed: 5
      Unnamed: 6

      0
      4
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      1
      0
      0.000000e+00
      0.000000
      0.0
      1.0
      1.0
      0.0

      2
      1
      6.000000e+00
      0.000000
      0.0
      0.0
      0.0
      0.0

      3
      2
      1.200000e+01
      0.000000
      0.0
      0.0
      0.0
      0.0

      4
      3
      1.500000e+01
      0.000000
      0.0
      0.0
      1.0
      0.0

      5
      1
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      6
      0
      7.000000e+07
      0.300000
      NaN
      NaN
      NaN
      NaN

      7
      1
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      8
      0
      1.200000e-02
      0.000023
      NaN
      NaN
      NaN
      NaN

      9
      3
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      10
      0
      0.000000e+00
      1.000000
      0.0
      0.0
      NaN
      NaN

      11
      1
      1.000000e+00
      2.000000
      0.0
      0.0
      NaN
      NaN

      12
      2
      2.000000e+00
      3.000000
      0.0
      0.0
      NaN
      NaN

      13
      2
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN

      14
      1
      0.000000e+00
      -20.000000
      0.0
      NaN
      NaN
      NaN

      15
      2
      0.000000e+00
      -20.000000
      0.0
      NaN
      NaN
      NaN

      <button class="colab-df-convert" onclick="convertToInteractive('df-da2173a1-51f8-4b65-abbb-e9e039ab3a6d')"
              title="Convert this dataframe to an interactive table."
              style="display:none;">

  <svg xmlns="http://www.w3.org/2000/svg" height="24px"viewBox="0 0 24 24"
       width="24px">
    <path d="M0 0h24v24H0V0z" fill="none"/>
    <path d="M18.56 5.44l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94zm-11 1L8.5 8.5l.94-2.06 2.06-.94-2.06-.94L8.5 2.5l-.94 2.06-2.06.94zm10 10l.94 2.06.94-2.06 2.06-.94-2.06-.94-.94-2.06-.94 2.06-2.06.94z"/><path d="M17.41 7.96l-1.37-1.37c-.4-.4-.92-.59-1.43-.59-.52 0-1.04.2-1.43.59L10.3 9.45l-7.72 7.72c-.78.78-.78 2.05 0 2.83L4 21.41c.39.39.9.59 1.41.59.51 0 1.02-.2 1.41-.59l7.78-7.78 2.81-2.81c.8-.78.8-2.07 0-2.86zM5.41 20L4 18.59l7.72-7.72 1.47 1.35L5.41 20z"/>
  </svg>
      </button>

```
#
# Variáveis do programa -- descreva os nomes das variáveis
#

# numnp          : número de pontos nodais
# neq            : número de equações
# coord          : coordenadas nodais
# idx            : array com os índices das equações
# nummat         : número de materiais

# E              : módulo de elasticidade
# nu             : coeficiente de poisson
# gp             : matriz com os dados definidos para cada uma das seções transversais
# nlcase         : número de casos de carregamento
# f              : vetor de forças nodais (global)
# matp           : propriedades dos materiais (A, E)
# conec          : matriz de conectividades
# geop           : array com as propriedades geométricas (A, I)
# numgp          : número de seções geométricas
```

# Informação dos nós, suas coordenadas e as condições de deslocabilidade

```
k=0
numnp = int(df.iloc[k,0])
coord=np.zeros((numnp,3))
idx=np.zeros((numnp,3), int)
k+=1

print("Número de nós da estrutura: {}".format(numnp))
print("Matriz com coordenadas (x,y,z) de cada um dos nós: \n{}".format(coord))
print("Matriz de possíveis deslocamentos em cada uma das direções x,y,z: \n{}".format(idx))
```

    Número de nós da estrutura: 4
    Matriz com coordenadas (x,y,z) de cada um dos nós:
    [[0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]]
    Matriz de possíveis deslocamentos em cada uma das direções x,y,z:
    [[0 0 0]
     [0 0 0]
     [0 0 0]
     [0 0 0]]

```
for i in range(numnp):
  print(df.iloc[k+i,:].values)
  v = df.iloc[k+i,:].values
  node=int(v[0])
  x,y,z = v[1:4]
  coord[node,:] =  v[1:4]
  idx[node,:] =  [ int(j) for j in v[4:7] ]
```

    [0. 0. 0. 0. 1. 1. 0.]
    [1. 6. 0. 0. 0. 0. 0.]
    [ 2. 12.  0.  0.  0.  0.  0.]
    [ 3. 15.  0.  0.  0.  1.  0.]

```
display(numnp, coord, idx)
```

# Leitura das propriedades dos materiais

```
k=1
k=k+numnp
nummat=df.iloc[k,0]
Ey=np.zeros(nummat)
nu=np.zeros(nummat)

print("Posicionamento na linha correta do arquivo: k={}".format(k))
print("Número de materiais definidos no arquivo de entrada de dados: {}".format(nummat))
print("Módulo de elasticidade para os materiais definidos: {}".format(Ey))
print("Coeficiente de Poisson para os materiais definidos: {}".format(nu))

k+=1
```

    Posicionamento na linha correta do arquivo: k=5
    Número de materiais definidos no arquivo de entrada de dados: 1
    Módulo de elasticidade para os materiais definidos: [0.]
    Coeficiente de Poisson para os materiais definidos: [0.]

```
for i in range(nummat):
  v=df.iloc[k+i].values  # linha do arquivo referente a um material: contém o módulo de elasticidade e o coeficiente de Poisson definido para o mesmo
  print(v)
  j=int(v[0])-1     # redução de uma unidade para alocação dos dados em estrutura interna
  Ey[j]=v[1]      # módulo de elasticidade do material correspondente
  nu[j]=v[2]
```

    [0.e+00 7.e+07 3.e-01    nan    nan    nan    nan]

# Leitura das informações das seções transversais

```
k=6
k=k+nummat
numgp = df.iloc[k,0]
gp=np.zeros((numgp,2))
k+=1
```

```
for i in range(numgp):
  v=df.iloc[k+i].values
  print(v)
  j=int(v[0])-1
  gp[j]=v[1:3]
```

    [0.00e+00 1.20e-02 2.25e-05      nan      nan      nan      nan]

# Leitura e armazenamento das informações dos elementos

```
k=8
k = k+numgp
nume = df.iloc[k,0]
conec=np.zeros((nume,2))
matp=np.zeros(nume)
geop=np.zeros(nume)
k+=1

print("Vetor de nós que conectam a barra: {}".format(conec))
print("Vetor que associa material à barra: {}".format(matp))
print("Vetor que associa seção à barra: {} \n\n".format(geop))

for i in range(nume):
  v=df.iloc[k+i].values
  conec[i,0] = v[1]
  conec[i,1] = v[2]
  matp[i] = v[3]
  geop[i] = v[4]

print("Vetor de nós que conectam a barra: \n{}".format(conec))
print("Vetor que associa material à barra: \n{}".format(matp))
print("Vetor que associa seção à barra: \n{}".format(geop))
```

    Vetor de nós que conectam a barra: [[0. 0.]
     [0. 0.]
     [0. 0.]]
    Vetor que associa material à barra: [0. 0. 0.]
    Vetor que associa seção à barra: [0. 0. 0.]

    Vetor de nós que conectam a barra:
    [[0. 1.]
     [1. 2.]
     [2. 3.]]
    Vetor que associa material à barra:
    [0. 0. 0.]
    Vetor que associa seção à barra:
    [0. 0. 0.]

# Contabilização do número de graus de liberdade do problema e como resultado temos o número de equações do sistema linear associado

```
neq=0
for n in range(numnp):
  # Para cada um dos possíveis deslocamentos representados
  # O vetor 'idx' armazena, agora, os índices dos graus de
  # liberdade com relação ao referencial global
  for i in range(3):
    if idx[n,i]==0:
      idx[n,i]=neq
      neq+=1
    else:
      idx[n,i]=-1
```

```
display(neq, idx)
```

# Leitura e armazenamento dos dados de carregamento nodal

```
k=10
k = k + nume
nload = df.iloc[k,0]
node  = np.zeros(nload)
idirn = np.zeros(nload)
fload = np.zeros(nload)

nlcase=1
r=np.zeros((neq,nlcase))
k+=1

print("Nós em que as cargas do caso correspondente serão aplicadas: \n{}".format(node))
print("Direções de aplicação válidas de cada carga: \n{}".format(idirn))
print("Magnitude das cargas aplicadas em cada uma das direções principais: \n{}".format(fload))
print("Vetor de cargas no referencial global: \n{}".format(r))
```

    Nós em que as cargas do caso correspondente serão aplicadas:
    [0. 0.]
    Direções de aplicação válidas de cada carga:
    [0. 0.]
    Magnitude das cargas aplicadas em cada uma das direções principais:
    [0. 0.]
    Vetor de cargas no referencial global:
    [[0.]
     [0.]
     [0.]
     [0.]
     [0.]
     [0.]
     [0.]
     [0.]
     [0.]]

# Processamento das carga nodais, transformando a informação local em informação global

```
k=14
l=0
r=np.zeros((neq,nlcase))
for i in range(nload):
  v=df.iloc[k+i].values                               # nó, Fx, Fy, Mz
  print(v)
  node = int(v[0])                                    # nó em que a carga está sendo aplicada
  fload = np.array([float(s) for s in v[1:4]])        # atribui as componentes de carga Fx, Fy, Mz
  idirn = np.array(idx[node])                         # vetor com os graus de liberdade do nó, -1 indica deslocamento impedido

  print (node,idirn,fload)
  for m in range(len(idirn)):
    if idirn[m] > -1:                                 # Verifica se a forca aplicada ira de fato influenciar na estrutura, verificar se não esta em cima do apoio
      r[idirn[m],l]=r[idirn[m],l] + fload[m]          # cálculo de r, que é um vetor das forças resultantes associadas a cada grau de liberdade
```

    [  1.   0. -20.   0.  nan  nan  nan]
    1 [1 2 3] [  0. -20.   0.]
    [  2.   0. -20.   0.  nan  nan  nan]
    2 [4 5 6] [  0. -20.   0.]

```
display(r)
```

    array([[  0.],
           [  0.],
           [-20.],
           [  0.],
           [  0.],
           [-20.],
           [  0.],
           [  0.],
           [  0.]])

# Cálculo de matriz de rigidez de cada elemento

```
# Função que monta a matriz de rigidez de um elemento qualquer
def localstiff(_E,_A,_I,_L,):
  x, u1, u2, u3, u4, u5, u6, L, E, I, A=sp.var ('x u1 u2 u3 u4 u5 u6 L E I A');

  # Deslocamentos axiais devido a algum carregamento
  u = [1-x/L, 0., 0., x/L, 0., 0.]

  # Deslocamentos verticais devido a algum carregamento em nós sem rótula
  v = [0., 1-3*(x/L)**2+2*(x/L)**3, x-2*((x**2)/L)+3*((x**3)/(L**2)), 0., 3*(x/L)**2 - 2*(x/L)**3, -(x**2)/L + (x**3)/(L**2)]

  # Do Princípio dos Trabalhos Virtuais tem-se W=U e chega-se em [K]{u}={F}
  K = sp.zeros(6) # matriz de rigidez local
  for i in range(6):
    for j in range(6):
      K[i,j] = (sp.integrate(sp.diff(E*I*v[i],x,2)*sp.diff(v[j],x,2),(x,0,L)) + sp.integrate(sp.diff(E*A*u[i],x,1)*sp.diff(u[j],x,1),(x,0,L)))

  KK= K.subs({E:_E,I:_I,A:_A,L:_L})
  KK=KK.evalf()
  return KK
```

# Função com a matriz de rotação para barras inclinadas

```
# Função que monta matriz de rotação para um quadro
def rotation_frame(_xyz):
  verbose=0
  xyz=np.array(_xyz)  # vetor com as coordenadas dos dois pontos do elemento
  l=0                 # tamanho do elemento

  # Calcula tamanho do elemento calculando a distância ente os dois pontos
  for i in range(3):
    l+=(xyz[i+3]-xyz[i])**2.

  l = np.sqrt(l) # l = sqrt((x1-x2)^2 + (y1-y2)^2 + (z1-z2)^2)
  c=np.zeros(3) # direção do elemento

  # Calcula direção unitária do elemento
  for i in range(3):
    c[i]=(xyz[i+3]-xyz[i])/l

  R=np.eye(6) # matriz de rotação

  # Preenchendo a matriz de rotação
  R[0,0]=R[1,1]=c[0]
  R[0,1]=c[1]
  R[1,0]=-c[1]
  R[3,3]=R[4,4]=c[0]
  R[3,4]=c[1]
  R[4,3]=-c[1]

  return(R)
```

# Montagem da matrix global do problema

```
K=np.zeros((neq,neq))
length=[]
barras=["AC","CD","DB"]
contador = 0

for n in range(nume):

  length.append((sum(coord[int(conec[n,0])]-coord[int(conec[n,1])])**2)**0.5)

  # Matriz local
  KK=localstiff(Ey[int(matp[n])], gp[int(geop[n])][0], gp[int(geop[n]),1], length[n])

  lm=[]  # lista com as direções globais dos nós locais
  xyz=[] # lista com as coordenadas globais dos nós locais

  for j in range(len(conec[n])):
    lm=lm+list(idx[int(conec[n,j])])
    xyz=xyz+list(coord[int(conec[n,j])])

  R=rotation_frame(xyz) # montagem da matriz local de rotação
  KL=np.zeros((6,6))    # cria a matriz local do elemento
  KL=R.T * KK * R       # montagem da matriz local do elemento no sistema de referência global

  print ("Matriz local", barras[contador])
  display (sp.Matrix(KL))

  # Adicionamos as matrizes locais KL na global K
  for i in range(len(lm)):
    if lm[i]!=-1:
      for j in range(len(lm)):
        if lm[j]!=-1:
          K[lm[i],lm[j]] +=  KL[i,j]

  if n!=nume-1:
    print("\n\n")
  contador+=1
```

    Matriz local AC

$\displaystyle \left[\begin{matrix}140000.0 & 0 & 0 & -140000.0 & 0 & 0\\0 & 87.5 & 787.5 & 0 & -87.5 & 262.5\\0 & 787.5 & 13650.0 & 0 & -787.5 & 3675.0\\-140000.0 & 0 & 0 & 140000.0 & 0 & 0\\0 & -87.5 & -787.5 & 0 & 87.5 & -262.5\\0 & 262.5 & 3675.0 & 0 & -262.5 & 1050.0\end{matrix}\right]$

    Matriz local CD

$\displaystyle \left[\begin{matrix}140000.0 & 0 & 0 & -140000.0 & 0 & 0\\0 & 87.5 & 787.5 & 0 & -87.5 & 262.5\\0 & 787.5 & 13650.0 & 0 & -787.5 & 3675.0\\-140000.0 & 0 & 0 & 140000.0 & 0 & 0\\0 & -87.5 & -787.5 & 0 & 87.5 & -262.5\\0 & 262.5 & 3675.0 & 0 & -262.5 & 1050.0\end{matrix}\right]$

    Matriz local DB

$\displaystyle \left[\begin{matrix}280000.0 & 0 & 0 & -280000.0 & 0 & 0\\0 & 700.0 & 3150.0 & 0 & -700.0 & 1050.0\\0 & 3150.0 & 27300.0 & 0 & -3150.0 & 7350.0\\-280000.0 & 0 & 0 & 280000.0 & 0 & 0\\0 & -700.0 & -3150.0 & 0 & 700.0 & -1050.0\\0 & 1050.0 & 7350.0 & 0 & -1050.0 & 2100.0\end{matrix}\right]$

```
print ("Matriz Global")
display (sp.Matrix(K))
```

    Matriz Global

$\displaystyle \left[\begin{matrix}13650.0 & 0.0 & -787.5 & 3675.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 280000.0 & 0.0 & 0.0 & -140000.0 & 0.0 & 0.0 & 0.0 & 0.0\\-787.5 & 0.0 & 175.0 & 525.0 & 0.0 & -87.5 & 262.5 & 0.0 & 0.0\\3675.0 & 0.0 & 525.0 & 14700.0 & 0.0 & -787.5 & 3675.0 & 0.0 & 0.0\\0.0 & -140000.0 & 0.0 & 0.0 & 420000.0 & 0.0 & 0.0 & -280000.0 & 0.0\\0.0 & 0.0 & -87.5 & -787.5 & 0.0 & 787.5 & 2887.5 & 0.0 & 1050.0\\0.0 & 0.0 & 262.5 & 3675.0 & 0.0 & 2887.5 & 28350.0 & 0.0 & 7350.0\\0.0 & 0.0 & 0.0 & 0.0 & -280000.0 & 0.0 & 0.0 & 280000.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 1050.0 & 7350.0 & 0.0 & 2100.0\end{matrix}\right]$

# Resolve o sistema  de equações para os **deslocamentos**

```
u = np.linalg.solve(K,r)
u
```

    array([[-0.09332674],
           [ 0.        ],
           [-0.9459219 ],
           [ 0.14394464],
           [ 0.        ],
           [-2.8045477 ],
           [-0.94829461],
           [ 0.        ],
           [ 4.72130499]])

```
print("Deslocamento horizontal do ponto C:\n{}".format(u[1][0]))
print("Deslocamento vertical do ponto C:\n{}".format(u[2][0]))
print("Rotação do ponto C: \n{}".format(u[3][0]))
```

    Deslocamento horizontal do ponto C:
    0.0
    Deslocamento vertical do ponto C:
    -0.9459218981710655
    Rotação do ponto C:
    0.1439446366782069

#Implementação da biblioteca anastruct para verificar os resultados

```
# Instalação dos pacotes para representação computacional
!pip install anastruct
```

    Collecting anastruct
      Downloading anastruct-1.2.0-py3-none-any.whl (69 kB)
         |████████████████████████████████| 69 kB 7.6 MB/s
    Requirement already satisfied: scipy>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from anastruct) (1.4.1)
    Requirement already satisfied: matplotlib>=3.0 in /usr/local/lib/python3.7/dist-packages (from anastruct) (3.2.2)
    Requirement already satisfied: numpy>=1.15.4 in /usr/local/lib/python3.7/dist-packages (from anastruct) (1.21.5)
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->anastruct) (1.3.2)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->anastruct) (3.0.7)
    Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->anastruct) (2.8.2)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=3.0->anastruct) (0.11.0)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib>=3.0->anastruct) (1.15.0)
    Installing collected packages: anastruct
    Successfully installed anastruct-1.2.0

```
# importando os pacotes necessários
import numpy as np
import matplotlib.pyplot as plt
from anastruct import SystemElements

#-------------------------------------------------------------------------------
# Informações da estrutura
#-------------------------------------------------------------------------------
node={"A":(0, 0), "C":(6, 0), "D":(12, 0), "B":(15, 0)}

# Define as conectividades entre os nós
conec=[('A', 'C'), ('C', 'D'), ('D', 'B')]

#-------------------------------------------------------------------------------
# Montagem do modelo estrutural
#-------------------------------------------------------------------------------
#  Instancia o sistema estrutural
EA = 70e+7 * 0.012   # kPa*m^2
EI = 70e+7 * 2.25e-5 # kPa*m^4
ss = SystemElements(EA=EA, EI=EI)
#ss = SystemElements()

# Implementa a conectividade entre os nós gerando os elementos ou "membros"
for e in conec:
  element = (node[e[0]], node[e[1]])
  ss.add_element(location = element)    # Add um elemento comum entre cada nó conectado

# Define os carregamentos pontuais
F = -20
node_C = ss.find_node_id(node['C'])
node_D = ss.find_node_id(node['D'])
ss.point_load(node_id=node_C, Fy=F) # Add um carregamento ao nó "C" da estrutura
ss.point_load(node_id=node_D, Fy=F) # Add um carregamento ao nó "D" da estrutura

# Apoios
node_id = ss.find_node_id(node['A'])
ss.add_support_hinged(node_id=node_id)
node_id = ss.find_node_id(node['B'])
ss.add_support_roll(node_id=node_id)

#-------------------------------------------------------------------------------
# Solução e resultados do sistema estrutural
#-------------------------------------------------------------------------------
ss.solve()

ss.show_structure(scale=0.7, figsize=(8,5), offset=(0,0))
#ss.show_reaction_force(scale=0.9, figsize=(10,7), offset=(0,1))
#ss.show_axial_force(scale=0.7, figsize=(10,7), offset=(0,0))
#ss.show_shear_force(scale=0.7, figsize=(10,7), offset=(0,0))
#ss.show_bending_moment(scale=0.7, figsize=(8,5), offset=(0,0))
ss.show_displacement(scale=0.7, figsize=(8,5), offset=(0,0))
```


Gotchas

From

To begin, we should make something about SymPy clear.  SymPy is nothing more than a Python library, like NumPy, Django, or even modules in the Python standard library sys or re.  What this means is that SymPy does not add anything to the Python language.  Limitations that are inherent in the Python language are also inherent in SymPy.  It also means that SymPy tries to use Python idioms whenever possible, making programming with SymPy easy for those already familiar with programming with Python.  As a simple example, SymPy uses Python syntax to build expressions.  Implicit multiplication (like 3x or 3 x) is not allowed in Python, and thus not allowed in SymPy. To multiply 3 and x, you must type 3*x with the *.

In Julia:

implicit multiplication by a literal is supported, unlike Python

Symbols

One consequence of this fact is that SymPy can be used in any environment where Python is available.  We just import it, like we would any other library:

```julia
    >>> from sympy import *
```

In Julia:

the functions from the sympy module are loaded with the package:

```julia
using SymPy
```

This imports all the functions and classes from SymPy into our interactive Python session.  Now, suppose we start to do a computation.

```julia
    >>> x + 1
    Traceback (most recent call last):
    ...
    NameError: name 'x' is not defined
```

In Julia:

the error output may differ, but an UndefVarError is thrown

```julia
x + 1
```

    UndefVarError(:x)

Oops! What happened here?  We tried to use the variable x, but it tells us that x is not defined.  In Python, variables have no meaning until they are defined.  SymPy is no different.  Unlike many symbolic manipulation systems you may have used, in SymPy, variables are not defined automatically. To define variables, we must use symbols.

```julia
    >>> x = symbols('x')
    >>> x + 1
    x + 1
```

In Julia:

```julia
x = symbols("x")
x + 1
```

\begin{equation*}x + 1\end{equation*}

symbols takes a string of variable names separated by spaces or commas, and creates Symbols out of them.  We can then assign these to variable names. Later, we will investigate some convenient ways we can work around this issue. For now, let us just define the most common variable names, x, y, and z, for use through the rest of this section

```julia
    >>> x, y, z = symbols('x y z')
```

In Julia:

```julia
x, y, z = symbols("x y z")
```

    (x, y, z)

As a final note, we note that the name of a Symbol and the name of the variable it is assigned to need not have anything to do with one another.

```julia
    >>> a, b = symbols('b a')
    >>> a
    b
    >>> b
    a
```

In Julia:

```julia
a, b = symbols("b a")
a
```

\begin{equation*}b\end{equation*}

```julia
b
```

\begin{equation*}a\end{equation*}

Here we have done the very confusing thing of assigning a Symbol with the name a to the variable b, and a Symbol of the name b to the variable a.  Now the Python variable named a points to the SymPy Symbol named b, and visa versa.  How confusing.  We could have also done something like

```julia
    >>> crazy = symbols('unrelated')
    >>> crazy + 1
    unrelated + 1
```

In Julia:

```julia
crazy = symbols("unrelated")
crazy + 1
```

\begin{equation*}unrelated + 1\end{equation*}

This also shows that Symbols can have names longer than one character if we want.

Usually, the best practice is to assign Symbols to Python variables of the same name, although there are exceptions:  Symbol names can contain characters that are not allowed in Python variable names, or may just want to avoid typing long names by assigning Symbols with long names to single letter Python variables.

To avoid confusion, throughout this tutorial, Symbol names and Python variable names will always coincide.  Furthermore, the word "Symbol" will refer to a SymPy Symbol and the word "variable" will refer to a Python variable.

Finally, let us be sure we understand the difference between SymPy Symbols and Python variables.  Consider the following::

```julia
  x = symbols('x')
  expr = x + 1
  x = 2
  print(expr)
```

What do you think the output of this code will be?  If you thought 3, you're wrong.  Let's see what really happens

```julia
    >>> x = symbols('x')
    >>> expr = x + 1
    >>> x = 2
    >>> print(expr)
    x + 1
```

In Julia:

we must change to double quotes (or use @vars x, say)

```julia
x = symbols("x")
expr = x + 1
x = 2
expr
```

\begin{equation*}x + 1\end{equation*}

Changing x to 2 had no effect on expr.  This is because x = 2 changes the Python variable x to 2, but has no effect on the SymPy Symbol x, which was what we used in creating expr.  When we created expr, the Python variable x was a Symbol.  After we created, it, we changed the Python variable x to 2.  But expr remains the same.  This behavior is not unique to SymPy.  All Python programs work this way: if a variable is changed, expressions that were already created with that variable do not change automatically.  For example

```julia
    >>> x = 'abc'
    >>> expr = x + 'def'
    >>> expr
    'abcdef'
    >>> x = 'ABC'
    >>> expr
    'abcdef'
```

In Julia:

The * infix operator is used for string concatenation

```julia
x = "abc"
expr = x * "def"
expr
```

abcdef

```julia
x = "ABC"
expr
```

abcdef

Quick Tip

To change the value of a Symbol in an expression, use subs

```julia
     >>> x = symbols('x')
     >>> expr = x + 1
     >>> expr.subs(x, 2)
     3
```

In Julia:

```julia
x = symbols("x")
expr = x + 1
expr.subs(x, 2)
```

\begin{equation*}3\end{equation*}

Or use the "call" form of subs: expr(x => 2)

In this example, if we want to know what expr is with the new value of x, we need to reevaluate the code that created expr, namely, expr = x + 1.  This can be complicated if several lines created expr.  One advantage of using a symbolic computation system like SymPy is that we can build a symbolic representation for expr, and then substitute x with values.  The correct way to do this in SymPy is to use subs, which will be discussed in more detail later.

```julia
    >>> x = symbols('x')
    >>> expr = x + 1
    >>> expr.subs(x, 2)
    3
```

In Julia:

```julia
x = symbols("x")
expr = x + 1
expr.subs(x, 2)
```

\begin{equation*}3\end{equation*}

TODO

Add link to basic operations section

Equals signs

Another very important consequence of the fact that SymPy does not extend Python syntax is that = does not represent equality in SymPy.  Rather it is Python variable assignment.  This is hard-coded into the Python language, and SymPy makes no attempts to change that.

You may think, however, that ==, which is used for equality testing in Python, is used for SymPy as equality.  This is not quite correct either.  Let us see what happens when we use ==.

```julia
    >>> x + 1 == 4
    False
```

In Julia:

== is similar as in Python:

```julia
x + 1 == 4
```

    false

Recall == promotes values, so we have a Julia object may be "equal" to a SymPy one:

```julia
0 == zero(Sym)  ## or Sym(0)
```

    true

Instead of treating x + 1 == 4 symbolically, we just got False.  In SymPy, == represents exact structural equality testing.  This means that a == b means that we are asking if a = b.  We always get a bool as the result of ==.  There is a separate object, called Eq, which can be used to create symbolic equalities

```julia
    >>> Eq(x + 1, 4)
    Eq(x + 1, 4)
```

In Julia:

```julia
Eq(x + 1, 4)
```

\begin{equation*}x + 1 = 4\end{equation*}

There is one additional caveat about == as well.  Suppose we want to know if $(x + 1)^2 = x^2 + 2x + 1$.  We might try something like this:

```julia
    >>> (x + 1)**2 == x**2 + 2*x + 1
    False
```

In Julia:

```julia
(x + 1)^2 == x^2 + 2*x + 1
```

    false

We got False again. However, $(x + 1)^2$ does equal $x^2 + 2x + 1$. What is going on here?  Did we find a bug in SymPy, or is it just not powerful enough to recognize this basic algebraic fact?

Recall from above that == represents exact structural equality testing. "Exact" here means that two expressions will compare equal with == only if they are exactly equal structurally.  Here, $(x + 1)^2$ and $x^2 + 2x + 1$ are not the same symbolically. One is the power of an addition of two terms, and the other is the addition of three terms.

It turns out that when using SymPy as a library, having == test for exact structural equality is far more useful than having it represent symbolic equality, or having it test for mathematical equality.  However, as a new user, you will probably care more about the latter two.  We have already seen an alternative to representing equalities symbolically, Eq.  To test if two things are equal, it is best to recall the basic fact that if a = b, then a - b = 0.  Thus, the best way to check if a = b is to take a - b and simplify it, and see if it goes to 0.  We will learn :ref:later <tutorial-simplify> that the function to do this is called simplify. This method is not infallible–-in fact, it can be theoretically proven <http://en.wikipedia.org/wiki/Richardson%27s_theorem>_ that it is impossible to determine if two symbolic expressions are identically equal in general–-but for most common expressions, it works quite well.

```julia
    >>> a = (x + 1)**2
    >>> b = x**2 + 2*x + 1
    >>> simplify(a - b)
    0
    >>> c = x**2 - 2*x + 1
    >>> simplify(a - c)
    4*x
```

In Julia:

```julia
a = (x + 1)^2
b = x^2 + 2*x + 1
simplify(a - b)
```

\begin{equation*}0\end{equation*}

```julia
c = x^2 - 2*x + 1
simplify(a - c)
```

\begin{equation*}4 x\end{equation*}

There is also a method called equals that tests if two expressions are equal by evaluating them numerically at random points.

```julia
    >>> a = cos(x)**2 - sin(x)**2
    >>> b = cos(2*x)
    >>> a.equals(b)
    True
```

In Julia:

```julia
a = cos(x)^2 - sin(x)^2
b = cos(2*x)
a.equals(b)
```

    true

Two Final Notes: ^ and /

You may have noticed that we have been using ** for exponentiation instead of the standard ^.  That's because SymPy follows Python's conventions.  In Python, ^ represents logical exclusive or.  SymPy follows this convention:

```julia
     >>> True ^ False
     True
     >>> True ^ True
     False
     >>> x^y
     Xor(x, y)
```

In Julia:

we export True and False for symbolic Boolean values

This does not apply, as we use ^ for exponentiation.

Use the prefix Or for logical

```julia
Or(True, False)
```

\begin{equation*}\mathrm{True}\end{equation*}

```julia
Or(True, True)
```

\begin{equation*}\mathrm{True}\end{equation*}

```julia
Or(x, y)
```

\begin{equation*}x \vee y\end{equation*}

Finally, a small technical discussion on how SymPy works is in order.  When you type something like x + 1, the SymPy Symbol x is added to the Python int 1.  Python's operator rules then allow SymPy to tell Python that SymPy objects know how to be added to Python ints, and so 1 is automatically converted to the SymPy Integer object.

This sort of operator magic happens automatically behind the scenes, and you rarely need to even know that it is happening.  However, there is one exception.  Whenever you combine a SymPy object and a SymPy object, or a SymPy object and a Python object, you get a SymPy object, but whenever you combine two Python objects, SymPy never comes into play, and so you get a Python object.

```julia
    >>> type(Integer(1) + 1)
    <class 'sympy.core.numbers.Integer'>
    >>> type(1 + 1)
    <... 'int'>
```

In Julia:

In Julia, most operations between SymPy objects and Julia objects will promote to a SymPy objects, but of course Julia objects combined will produce Julia Objects:

```julia
typeof(sympy.Integer(1) + 1)
```

    SymPy.Sym

```julia
typeof(1 + 1)
```

    Int64

To convert a Julia object to a SymPy object, the Sym constructor may be useful:

```julia
Sym(1)
```

\begin{equation*}1\end{equation*}

To convert a SymPy object to a Julia object, the N function is useful for numbers and booleans:

```julia
N(Sym(1)), N(True)
```

    (1, true)

And the lambdify function can produce a function from an expression:

```julia
ex = x^2 - 2x + 2
fn = lambdify(ex)
fn(1) - ex(1)
```

\begin{equation*}0\end{equation*}

Note

On running the example above in SymPy Live, (1+1) is wrapped    by Integer, so it does not show the correct output.

This is usually not a big deal. Python ints work much the same as SymPy Integers, but there is one important exception:  division.  In SymPy, the division of two Integers gives a Rational:

```julia
    >>> Integer(1)/Integer(3)
    1/3
    >>> type(Integer(1)/Integer(3))
    <class 'sympy.core.numbers.Rational'>
```

In Julia:

```julia
sympy.Integer(1)/sympy.Integer(3)
```

\begin{equation*}\frac{1}{3}\end{equation*}

```julia
typeof(sympy.Integer(1)/sympy.Integer(3))
```

    SymPy.Sym

And to get the Python, type, we can use __class__:

```julia
(sympy.Integer(1)/sympy.Integer(3)).__class__
```

    PyObject <class 'sympy.core.numbers.Rational'>

But in Python / represents either integer division or floating point division, depending on whether you are in Python 2 or Python 3, and depending on whether or not you have run from __future__ import division:

```julia
    >>> from __future__ import division
    >>> 1/2 #doctest: +SKIP
    0.5
```

In Julia:

This does not apply, as / is not integer division.

!! note

On running the example above in SymPy Live, (1/2) is wrapped    by Integer, so it does not show the correct output.

To avoid this, we can construct the rational object explicitly

```julia
    >>> Rational(1, 2)
    1/2
```

In Julia:

Rational from sympy is not exported, it would conflict with Julia's Rational costructor. We must qualify it:

```julia
Rational(1, 2)
```

    1//2

```julia
sympy.Rational(1, 2)
```

\begin{equation*}\frac{1}{2}\end{equation*}

This problem also comes up whenever we have a larger symbolic expression with int/int in it.  For example:

```julia
    >>> x + 1/2 #doctest: +SKIP
    x + 0.5
```

In Julia:

Int/Int will produce a floating point value, whereas Int//Int will produce a rational, which can then be promoted without loss to a symbolic object:

```julia
x + 1/2 #doctest: +SKIP
```

\begin{equation*}x + 0.5\end{equation*}

Note

On running the example above in SymPy Live, (1/2) is wrapped    by Integer, so it does not show the correct output.

This happens because Python first evaluates 1/2 into 0.5, and then that is cast into a SymPy type when it is added to x.  Again, we can get around this by explicitly creating a Rational:

```julia
    >>> x + Rational(1, 2)
    x + 1/2
```

In Julia:

```julia
x + 1//2
```

\begin{equation*}x + \frac{1}{2}\end{equation*}

There are several tips on avoiding this situation in the :ref:gotchas document.

Further Reading

For more discussion on the topics covered in this section, see :ref:gotchas.

return to index