*best viewed in [nbviewer](https://nbviewer.jupyter.org/github/CambridgeSemiticsLab/BH_time_collocations/blob/master/results/notebooks/yqtl_association_with_advb.ipynb)*

# Yiqṭol and Its Association with Prototypical Adverbs
### Cody Kingham

```python
! echo "last updated:"; date
```

    last updated:
    Thu 27 Feb 2020 10:58:59 GMT

## Why does yiqṭol prefer particles?

In [a preliminary analysis](https://nbviewer.jupyter.org/github/CambridgeSemiticsLab/BH_time_collocations/blob/master/archive/2019-10-31/analysis/exploratory/construction_clusters.ipynb), we found evidence that the yiqṭol verb tends to be attracted to time words which can be considered "prototypical adverbs". These are words which are infrequently combined with constructions that indicate number, definiteness, or possession (see Croft, "A Conceptual Frameowork for Grammatical Categories," 1990). These prototypical adverbs include words such as אז ,נצח ,עולם ,מתי ,עתה. In [the study on parts of speech](https://nbviewer.jupyter.org/github/CambridgeSemiticsLab/BH_time_collocations/blob/master/results/notebooks/time_distribution_and_pos.ipynb), we found an empirical basis for separating these words from their noun counterparts on the grounds of their collocability with nominalizing constructions.

The preliminary analysis cited above found a statistical association between yiqṭol and prototypical adverbs. **In this notebook, we seek to confirm that yiqṭol indeed has a preference for these adverb terms, and we ask why?**

### Why does it matter?

One of the surprising things about this pattern, as this notebook will demonstrate, is that not all of the yiqṭol adverbs seem to fit the same semantic mold. For instance, the semantics of אז, which refers to a point in time, is different from עולם, which refers to a unknown duration. And yet, these words share two major qualities: they are both adverb-like, and they both seem to prefer the yiqṭol verb (as we will seek to confirm). This is surprising because we might assume that yiqṭol's preference is primarily semantic in nature: durational terms such as עולם and נצח would fit the widely-agreed, unbounded meaning of the verbal form. But אז suggests another possibility: that yiqṭol's preference is not wholly predictable from its semantics, but possibly due to a convention of use. Or, in other words, it is simply an idiosyncratic quality of yiqṭol to easily combine with adverbs. This would not mean such behavior has *no* semantic motivation. Perhaps the association of yiqṭol with durational adverbs opened the door for an association with adverbs in general. **These questions are purely hypothetical. And in this notebook we will seek to find empirical data that can help (in)validate these hypotheses and suggest further directions for analysis.**

# Python

Now we import the modules and data needed for the analysis.

```python
# standard packages
from pathlib import Path
import collections
import networkx as nx
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['font.serif'] = ['SBL Biblit']
import seaborn as sns
from bidi.algorithm import get_display # bi-directional text support for plotting
from scipy.stats import linregress

# custom package in /tools
from paths import main_table, figs
from tf_tools.load import load_tf
from tf_tools.tokenizers import tokenize_surface
from cx_analysis.load import cxs
from cx_analysis.search import SearchCX
from stats.significance import contingency_table, apply_fishers
from stats.pca import apply_pca

TF, API, A = load_tf(silent='deep')
A.displaySetup(condenseType='phrase')
F, E, T, L = A.api.F, A.api.E, A.api.T, A.api.L
se = SearchCX(A)
phrase2cxs = cxs['phrase2cxs']
class2cx = cxs['class2cx']
time_cxs = list(phrase2cxs.values())

# configure data output
name = 'yqtl_association_with_advb'
figures = figs.joinpath(name)
if not figures.exists():
    figures.mkdir(parents=True)
def fg_path(fg_name):
    return figures.joinpath(fg_name)
```

```python
# set up project dataset
times_full = pd.read_csv(main_table, sep='\t')
times_full.set_index(['node'], inplace=True)
times = times_full[~times_full.classi.str.contains('component')]
```

# Adverb Collocations with Yiqṭol

We pick up the thread where we left off in [the notebook on parts of speech](https://nbviewer.jupyter.org/github/CambridgeSemiticsLab/BH_time_collocations/blob/master/results/notebooks/time_distribution_and_pos.ipynb). There we used Principal Component Analysis (PCA) to isolate prototypical adverbs from nouns. We duplicate that same method here and begin to look at the collocational tendencies of these items compared against verbs.

```python
# compile collocation table of time heads with nominalizing constructions
time_cols = pd.pivot_table(
    times,
    index=['time'],
    values=['time_pl', 'quantified', 'definite',
            'demonstrative', 'ordinal', 'time_sffx',
            'bare', 'genitive'],
    aggfunc=np.sum
)

# sort on size
time_cols = time_cols.loc[time_cols.sum(1).sort_values(ascending=False).index]

# drop observations with < 5 total sample size
time_cols = time_cols[time_cols.sum(1) > 4]

# calculate ratio
time_col_ratio = time_cols.div(time_cols.sum(1), axis=0)

# apply PCA to dataset
time_pca, time_loadings = apply_pca(time_col_ratio, 0, 1, scree=False)
```

The results of the PCA analysis and its implications are explored and explained further in the notebook linked to above. The resulting PCA space is illustrated below, as calculated in that notebook.

The items to the right of the plot are the "adverb" group. We can select them by pulling all words with an x-axis value (principal component 1 or PC1) which is $> 0$. We can generalize and say that in this model terms which have a higher x-axis value are "more adverbial." Although, this categorization remains a working hypothesis. Those forms are shown below in descending order. Note that all words down to עתה have the same value of `0.59`.

```python
advbs = time_pca[time_pca['PC1'] > 0].iloc[:, :2]
advbs = advbs.sort_values(by='PC1', ascending=False) # sort by distance from origin

advbs
```

      PC1
      PC2

      time

      יומם
      0.594544
      0.016065

      מתי
      0.594544
      0.016065

      טרם
      0.594544
      0.016065

      מהר
      0.594544
      0.016065

      אן
      0.594544
      0.016065

      מהרה
      0.594544
      0.016065

      עד
      0.594544
      0.016065

      שׁני
      0.594544
      0.016065

      פתאם
      0.594544
      0.016065

      הנה
      0.594544
      0.016065

      מחר
      0.594544
      0.016065

      תמיד
      0.594544
      0.016065

      כן
      0.594544
      0.016065

      אז
      0.594544
      0.016065

      עתה
      0.589188
      0.015674

      עוד
      0.583211
      0.013436

      נצח
      0.555681
      0.011931

      עולם
      0.536794
      0.011730

      מחרת
      0.344338
      0.023228

      רגע
      0.199177
      -0.121825

By contrast, we can also select a list of nouns, which lie on the opposite end of the spectrum. We see that a few terms are clustered towards the center, such as ראשׁית and אחר. Since their classification is mixed, we will not select those. We will thus select all items which have $PC1 < -0.2$

```python
nouns = time_pca[time_pca['PC1'] < -0.2].iloc[:, :2]
nouns = nouns.sort_values(by='PC1')

nouns
```

      PC1
      PC2

      time

      ערב
      -0.580466
      0.626619

      שׁבת
      -0.569903
      0.567700

      צהרים
      -0.544289
      0.174998

      מועד
      -0.531623
      0.261975

      ראשׁון
      -0.515870
      0.677366

      שׁבוע
      -0.507633
      -0.183260

      פעם
      -0.504261
      -0.184201

      רגל
      -0.496096
      -0.423423

      תחלה
      -0.492899
      0.238149

      יום
      -0.490936
      0.096292

      שׁבע
      -0.489334
      -0.302840

      שׁנה
      -0.488740
      -0.229504

      פנה
      -0.483370
      -0.499132

      דבר
      -0.482846
      0.040616

      חדשׁ
      -0.467445
      -0.102637

      חיים
      -0.457386
      -0.439302

      נעורים
      -0.457386
      -0.439302

      דור
      -0.453976
      -0.385566

      בקר
      -0.450121
      0.606823

      ירח
      -0.447976
      -0.224210

      עת
      -0.446741
      0.166803

      מות
      -0.414364
      -0.184939

      לילה
      -0.246892
      0.294790

## Compare Verb Tense Collocational Tendencies

We will now make a count of these two categories' collocational tendencies with certain verb tenses. We depend on the [BHSA's](https://github.com/ETCBC/bhsa) encoding of the verbs, which distinguish the following possible categories:

| name | tag |
| ---- | --- |
| qaṭal | qtl |
| yiqṭol| yqtl |
| wayyiqṭol | wyqtl |
| imperative | impv |
| participle | ptcp |
| infinitive | inf |

Note that the tense names/tags have been adapted from BHSA terms like "imperfect" to fit the more theory-neutral terminology. A further category has been added by this project's code, that of the `weqetal` (see the production code [here](../../data/bhsa/tenses.py)):

| name | tag |
| ---  | --- |
| weqeṭal | wqtl |

For this analysis, we will focus on `qtl`, `yqtl`, `wyqtl`, `wqtl`, and `ptcp`, leaving aside the imperative and infinitives for now. We do not yet distinguish verb tenses on the basis of positionality or length (e.g. long versus short yiqṭol).

### Calculate and compare tense tendencies with adverbs / nouns

First we build a table, `tense_cols` which contains tense collocation counts.

```python
tense_cols_full = times.pivot_table(index=['time'], columns=['tense'], aggfunc='size').fillna(0) # get collocations
tense_cols = tense_cols_full[['qtl', 'wqtl', 'wyqtl', 'yqtl', 'ptcp',]] # select subset
tense_cols = tense_cols.loc[tense_cols.sum(1).sort_values(ascending=False).index] # sort by frequency
tense_ratios = tense_cols.divide(tense_cols.sum(1), axis=0) # get ratios
tense_fishers, tense_odds = apply_fishers(tense_cols, 0, 1) # Fisher's association scores
```

The top of the collocation dataset is shown below, with **raw counts**.

```python
tense_cols.head()
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      יום
      318.0
      96.0
      257.0
      329.0
      89.0

      עוד
      42.0
      8.0
      48.0
      198.0
      24.0

      שׁנה
      97.0
      11.0
      66.0
      36.0
      3.0

      עולם
      23.0
      19.0
      6.0
      77.0
      9.0

      עת
      29.0
      6.0
      31.0
      42.0
      12.0

We also have a table which calculates the **ratio representation of each verb per time**, shown below:

```python
tense_ratios.head()
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      יום
      0.292011
      0.088154
      0.235996
      0.302112
      0.081726

      עוד
      0.131250
      0.025000
      0.150000
      0.618750
      0.075000

      שׁנה
      0.455399
      0.051643
      0.309859
      0.169014
      0.014085

      עולם
      0.171642
      0.141791
      0.044776
      0.574627
      0.067164

      עת
      0.241667
      0.050000
      0.258333
      0.350000
      0.100000

The ratios alone tell us a verb's representation within a time's own occurrences, but it does not tell us how significant those values are with respect to a verb's overall frequency and co-occurrence with other times. For that we can use [Fisher's exact test](https://en.wikipedia.org/wiki/Fisher%27s_exact_test), which is uniquely suited for non-normal distributions (as we see in natural language data) and datasets with values $< 5$. The Fisher's test returns a `p-value`, which we in turn transform with a $log_{10}$, following [Stefanowitsch and Gries 2003](https://www.researchgate.net/publication/37929828_Collostructions_Investigating_the_interaction_of_words_and_constructions). The null hypothesis that "there is no association between A and B" can be rejected when $p < 1.3$, that is $|\log_{10}0.05|$. We also add a positive or negative sign based on whether the observed frequency falls below the expected frequency, again following Stefanowitsch and Gries. The top of the **Fisher's Exact** table is shown below.

```python
tense_fishers.head()
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      יום
      1.586072
      2.950021
      1.045879
      -9.452700
      1.125428

      עוד
      -9.080649
      -3.247572
      -2.896506
      19.778072
      0.137591

      שׁנה
      9.001763
      -0.401541
      2.853509
      -10.911849
      -3.646659

      עולם
      -2.030842
      2.674619
      -7.666214
      5.618482
      0.000000

      עת
      -0.222696
      -0.238904
      0.508451
      -0.200192
      0.699389

### Separate "adverb" dataset from "noun" dataset

Now that we have a dataset with verb collocation data, we test whether there are strong tendencies for each group of words as identified by the PCA analysis. First we isolate the potential adverbs from the other times.

```python
# adverbs
advb_tenses = tense_cols.loc[advbs.index]
advb_tenses = advb_tenses.loc[advb_tenses.sum(1).sort_values(ascending=False).index] # sort by N-observations
advb_ratio = tense_ratios.loc[advb_tenses.index]
advb_fishers = tense_fishers.loc[advb_tenses.index]

# nouns
noun_tenses = tense_cols.loc[nouns.index]
noun_tenses = noun_tenses.loc[noun_tenses.sum(1).sort_values(ascending=False).index] # sort by N-observations
noun_ratio = tense_ratios.loc[noun_tenses.index]
noun_fishers = tense_fishers.loc[noun_tenses.index]
```

Below is an exhaustive inventory of raw tense collocation counts for all of the "adverbs."

```python
advb_tenses
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      עוד
      42.0
      8.0
      48.0
      198.0
      24.0

      עולם
      23.0
      19.0
      6.0
      77.0
      9.0

      אז
      43.0
      0.0
      1.0
      72.0
      3.0

      עתה
      45.0
      0.0
      0.0
      44.0
      2.0

      כן
      11.0
      0.0
      21.0
      18.0
      0.0

      תמיד
      2.0
      6.0
      0.0
      18.0
      6.0

      מחר
      1.0
      1.0
      0.0
      20.0
      4.0

      נצח
      4.0
      0.0
      0.0
      20.0
      1.0

      מחרת
      0.0
      0.0
      21.0
      2.0
      0.0

      פתאם
      5.0
      1.0
      4.0
      12.0
      1.0

      מתי
      2.0
      0.0
      0.0
      16.0
      2.0

      שׁני
      0.0
      3.0
      12.0
      2.0
      0.0

      יומם
      5.0
      1.0
      1.0
      8.0
      1.0

      עד
      0.0
      0.0
      0.0
      11.0
      3.0

      אן
      2.0
      0.0
      0.0
      11.0
      1.0

      טרם
      2.0
      0.0
      0.0
      9.0
      0.0

      רגע
      4.0
      0.0
      0.0
      7.0
      0.0

      מהרה
      2.0
      2.0
      2.0
      3.0
      0.0

      מהר
      4.0
      2.0
      0.0
      1.0
      0.0

      הנה
      2.0
      0.0
      0.0
      1.0
      1.0

The numbers are summarized below. For reference, `count` refers to number of non-zero observations. `25%` etc. refer to the percentile. The other values are self-explanatory.

```python
advb_tenses.describe()
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      count
      20.000000
      20.000000
      20.000000
      20.000000
      20.000000

      mean
      9.950000
      2.150000
      5.800000
      27.500000
      2.900000

      std
      15.271146
      4.522168
      11.954299
      45.555634
      5.485867

      min
      0.000000
      0.000000
      0.000000
      1.000000
      0.000000

      25%
      2.000000
      0.000000
      0.000000
      6.000000
      0.000000

      50%
      3.000000
      0.000000
      0.000000
      11.500000
      1.000000

      75%
      6.500000
      2.000000
      4.500000
      20.000000
      3.000000

      max
      45.000000
      19.000000
      48.000000
      198.000000
      24.000000

The ratios are shown below:

```python
advb_ratio
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      עוד
      0.131250
      0.025000
      0.150000
      0.618750
      0.075000

      עולם
      0.171642
      0.141791
      0.044776
      0.574627
      0.067164

      אז
      0.361345
      0.000000
      0.008403
      0.605042
      0.025210

      עתה
      0.494505
      0.000000
      0.000000
      0.483516
      0.021978

      כן
      0.220000
      0.000000
      0.420000
      0.360000
      0.000000

      תמיד
      0.062500
      0.187500
      0.000000
      0.562500
      0.187500

      מחר
      0.038462
      0.038462
      0.000000
      0.769231
      0.153846

      נצח
      0.160000
      0.000000
      0.000000
      0.800000
      0.040000

      מחרת
      0.000000
      0.000000
      0.913043
      0.086957
      0.000000

      פתאם
      0.217391
      0.043478
      0.173913
      0.521739
      0.043478

      מתי
      0.100000
      0.000000
      0.000000
      0.800000
      0.100000

      שׁני
      0.000000
      0.176471
      0.705882
      0.117647
      0.000000

      יומם
      0.312500
      0.062500
      0.062500
      0.500000
      0.062500

      עד
      0.000000
      0.000000
      0.000000
      0.785714
      0.214286

      אן
      0.142857
      0.000000
      0.000000
      0.785714
      0.071429

      טרם
      0.181818
      0.000000
      0.000000
      0.818182
      0.000000

      רגע
      0.363636
      0.000000
      0.000000
      0.636364
      0.000000

      מהרה
      0.222222
      0.222222
      0.222222
      0.333333
      0.000000

      מהר
      0.571429
      0.285714
      0.000000
      0.142857
      0.000000

      הנה
      0.500000
      0.000000
      0.000000
      0.250000
      0.250000

### Ratio across all "adverbs" (versus "nouns")

We can see in the summary below that the adverb group has a high proportion of yiqtol representation:

```python
all_advb_tenses = advb_tenses.sum() / advb_tenses.sum().sum()

all_advb_tenses
```

    tense
    qtl      0.206004
    wqtl     0.044513
    wyqtl    0.120083
    yqtl     0.569358
    ptcp     0.060041
    dtype: float64

Let's see how this compares against the "nouns" group.

```python
all_noun_tenses = noun_tenses.sum() / noun_tenses.sum().sum()

all_noun_tenses
```

    tense
    qtl      0.286929
    wqtl     0.085016
    wyqtl    0.276833
    yqtl     0.279490
    ptcp     0.071732
    dtype: float64

We get the difference between the two distributions:

```python
noun_advb_difference = (all_advb_tenses - all_noun_tenses).abs()

noun_advb_difference
```

    tense
    qtl      0.080925
    wqtl     0.040502
    wyqtl    0.156750
    yqtl     0.289868
    ptcp     0.011691
    dtype: float64

The biggest difference is the yiqtol's representation at ~30% more within the adverb group.

### "Adverbs" and Fisher's exact tests

The Fisher's scores for adverbs are show below. $1.3 < score < -1.3$ is statistically significant:

#### Association with any tense

```python
advb_fishers
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      עוד
      -9.080649
      -3.247572
      -2.896506
      19.778072
      0.137591

      עולם
      -2.030842
      2.674619
      -7.666214
      5.618482
      0.000000

      אז
      1.586010
      -3.301144
      -11.248105
      6.498397
      -1.351648

      עתה
      5.521070
      -2.606705
      -9.760595
      1.439333
      -1.042144

      כן
      -0.282993
      -1.331073
      2.833125
      -0.053707
      -1.329310

      תמיד
      -2.117939
      1.737141
      -3.124449
      1.380447
      1.662020

      מחר
      -2.217454
      0.000000
      -2.525332
      4.286143
      0.977794

      נצח
      -0.578327
      -0.387902
      -2.537950
      4.617990
      0.000000

      מחרת
      -2.876783
      -0.398917
      11.758661
      -2.416684
      -0.395153

      פתאם
      -0.089793
      0.000000
      -0.096298
      0.713563
      0.000000

      מתי
      -0.899719
      -0.404130
      -1.917864
      3.687975
      0.188855

      שׁני
      -1.994594
      0.987496
      4.640552
      -1.388484
      -0.202828

      יומם
      0.109501
      0.000000
      -0.656254
      0.506937
      0.000000

      עד
      -1.549583
      -0.208964
      -1.301094
      2.460708
      1.154166

      אן
      -0.423193
      -0.208964
      -1.301094
      2.460708
      -0.000000

      טרם
      -0.132099
      0.000000
      -0.865613
      2.452161
      0.000000

      רגע
      0.302056
      0.000000
      -0.865613
      0.941003
      0.000000

      מהרה
      0.000000
      0.921577
      -0.000000
      0.000000
      0.000000

      מהר
      1.056650
      1.117225
      -0.445077
      -0.573658
      0.000000

      הנה
      0.534929
      0.000000
      -0.234763
      0.000000
      0.596753

#### Associations with yiqtol

We select those scores where $score > 1.3$ for the yiqtol verb.

```python
advb_yqtl_assoc = advb_fishers[advb_fishers['yqtl'] > 1.3].sort_values(by='yqtl', ascending=False)

advb_yqtl_assoc
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      עוד
      -9.080649
      -3.247572
      -2.896506
      19.778072
      0.137591

      אז
      1.586010
      -3.301144
      -11.248105
      6.498397
      -1.351648

      עולם
      -2.030842
      2.674619
      -7.666214
      5.618482
      0.000000

      נצח
      -0.578327
      -0.387902
      -2.537950
      4.617990
      0.000000

      מחר
      -2.217454
      0.000000
      -2.525332
      4.286143
      0.977794

      מתי
      -0.899719
      -0.404130
      -1.917864
      3.687975
      0.188855

      עד
      -1.549583
      -0.208964
      -1.301094
      2.460708
      1.154166

      אן
      -0.423193
      -0.208964
      -1.301094
      2.460708
      -0.000000

      טרם
      -0.132099
      0.000000
      -0.865613
      2.452161
      0.000000

      עתה
      5.521070
      -2.606705
      -9.760595
      1.439333
      -1.042144

      תמיד
      -2.117939
      1.737141
      -3.124449
      1.380447
      1.662020

```python
fig = plt.figure(figsize=(8, 6))
ax = sns.heatmap(advb_yqtl_assoc, center=0, robust=True)
ax.set_yticklabels([get_display(t) for t in advb_yqtl_assoc.index], size=16, rotation=0, fontfamily='serif')
ax.set_ylabel('time word')
ax.set_xlabel('verb tense')
plt.show()
```

#### Disassociations with yiqtol

We select scores where $score < -1.3$. This means that the observed co-occurrence frequency is lower than would be expected in a random distribution.

```python
advb_yqtl_dassoc =  advb_fishers[advb_fishers['yqtl'] < -1.3].sort_values(by='yqtl', ascending=False)

advb_yqtl_dassoc
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      שׁני
      -1.994594
      0.987496
      4.640552
      -1.388484
      -0.202828

      מחרת
      -2.876783
      -0.398917
      11.758661
      -2.416684
      -0.395153

Only two terms are marked as especially disassociated with the verb: שני and מחרת. We see that both are statistically attracted to wayyiqtol. And likewise disassociated with qatal. There is no statistically significant values for participles and weqatals.

#### Neutral scores with respect to yiqtol

What terms are neutral to the yiqtol? i.e. they are neither associated nor disassociated.

```python
advb_yqtl_neut = advb_fishers[(advb_fishers['yqtl'] < 1.3) & (advb_fishers['yqtl'] > -1.3)].sort_values(by='yqtl', ascending=False)

advb_yqtl_neut
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      רגע
      0.302056
      0.000000
      -0.865613
      0.941003
      0.000000

      פתאם
      -0.089793
      0.000000
      -0.096298
      0.713563
      0.000000

      יומם
      0.109501
      0.000000
      -0.656254
      0.506937
      0.000000

      מהרה
      0.000000
      0.921577
      -0.000000
      0.000000
      0.000000

      הנה
      0.534929
      0.000000
      -0.234763
      0.000000
      0.596753

      כן
      -0.282993
      -1.331073
      2.833125
      -0.053707
      -1.329310

      מהר
      1.056650
      1.117225
      -0.445077
      -0.573658
      0.000000

Some of these terms refer to suddenness: מהר, מהרה, פתאם. Others refer to positions in time like רגע. We also see יומם.

We do not see any terms that refer to extended durations or open ended/unknown times, as we did with those that are attracted to yiqtol.

### "Noun" associations with yiqtol

```python
noun_fishers
```

      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      יום
      1.586072
      2.950021
      1.045879
      -9.452700
      1.125428

      שׁנה
      9.001763
      -0.401541
      2.853509
      -10.911849
      -3.646659

      עת
      -0.222696
      -0.238904
      0.508451
      -0.200192
      0.699389

      לילה
      -0.507048
      -0.497665
      10.457816
      -7.354929
      0.488659

      בקר
      -7.399966
      0.175056
      9.841146
      -0.821145
      -0.079382

      ערב
      -2.766337
      7.771832
      -0.250691
      0.094592
      -0.627759

      פעם
      -0.125087
      1.093670
      0.913084
      -0.834382
      -0.236854

      חדשׁ
      0.472044
      0.000000
      1.013502
      -1.885576
      0.134547

      מות
      0.092548
      0.000000
      1.876936
      -0.952690
      -0.403455

      פנה
      4.118958
      -0.208964
      -1.301094
      -1.633336
      0.588294

      דבר
      0.000000
      0.000000
      2.644836
      -0.825327
      0.000000

      צהרים
      -0.697813
      0.715082
      0.135309
      0.415514
      0.000000

      נעורים
      0.910678
      0.000000
      -0.227482
      -0.784677
      1.369430

      שׁבע
      -0.477258
      0.000000
      -0.227482
      1.158950
      0.514756

      חיים
      0.000000
      0.000000
      0.000000
      0.431032
      0.000000

      שׁבוע
      -0.477258
      1.400536
      -0.227482
      0.431032
      0.000000

      ראשׁון
      1.697273
      0.000000
      -0.227482
      -0.182894
      0.000000

      מועד
      0.000000
      0.000000
      -0.234763
      0.197910
      0.596753

      שׁבת
      0.000000
      0.000000
      -0.234763
      0.000000
      1.570864

      תחלה
      0.534929
      0.000000
      -0.000000
      -0.517269
      0.596753

      רגל
      0.534929
      0.000000
      -0.000000
      0.000000
      0.000000

      דור
      0.000000
      0.611711
      -0.234763
      0.197910
      0.000000

      ירח
      0.000000
      0.611711
      0.676962
      -0.517269
      0.000000

```python
fig = plt.figure(figsize=(8, 10))
ax = sns.heatmap(noun_fishers, center=0, robust=True)
ax.set_yticklabels([get_display(t) for t in noun_fishers.index], size=16, rotation=0, fontfamily='serif')
ax.set_ylabel('time word')
ax.set_xlabel('verb tense')
plt.show()
```

## Scatter Plots of Variables

### PC1 versus Yiqṭol

I'm curious to test whether distance along PC1 space correlates with frequency of use with the yiqṭol verb. We will make a scatter plot of the two values to see if there's any noticeable trend.

```python
pc1_and_yqtl = pd.concat([tense_ratios['yqtl'], time_pca['PC1']], axis=1, sort=True).dropna()
```

```python
x, y = pc1_and_yqtl['PC1'], pc1_and_yqtl['yqtl']
slope, intercept, r, p, std_err = linregress(x,y)
fig, ax = plt.subplots(figsize=(8,6))
ax.set_xlabel('PC1', size=14)
ax.set_ylabel('yqtl', size=14)
ax.scatter(x=x, y=y, color='black', s=8)

# plot regression line
r_x = np.unique(x.values) # avoid duplicate points
r_y = slope*r_x+intercept
ax.plot(r_x, r_y, color='red', linewidth=1.2)

ax.set_title(f'PC1 vs. yqtl (r={round(r, 2)}) (p={round(p, 4)})')
plt.show()
```

We do see a trend for more yiqtol representation in times that are further along the first principle component. Despite there being some exceptions, our $p=0.00021$ shows the correlation to be significant.

This correlation is important because it potentially shows another distinguishing feature of these "adverb" time words: **they are not only distinguished by their use without nominalizing constructions, they are also distinguished by their preference for the yiqtol verb form.** This confirms the data we have already seen in the dominant proportional representation of yiqtol in this group, as well as in the Fisher's exact scores.

### Time construction co-occurrence preferences versus yiqtol

We dig down a little bit deeper. Can we corroborate the principle component analysis by looking at word tendencies throughout the various constructions?

For this analysis, for every time word, we take the ratio of its co-occurrence with nominalizing constructions and its ratio of tense co-occurrences. We want to answer: **Are times that occur more/less often with construction X more/less likely to co-occur with the yiqtol**?

We then build a separate scatter plot for each one to see whether there is a negative or positive correlation.

```python
sample = time_cols[time_cols.sum(1) > 10]
sample_tenses = tense_ratios.loc[sample.index]
sample_cols = time_col_ratio.loc[sample.index]
```

```python
do_cxs = [
    'definite', 'demonstrative',
    'quantified', 'time_pl', 'genitive',
    'bare',
]

nom_yiq_dataset = {}

for cx in do_cxs:
    cx_data = sample_cols[cx]
    tense_data = sample_tenses['yqtl']
    tense_and_cx = pd.concat([cx_data, tense_data], sort=False, axis=1).fillna(0)
    tense_and_cx = tense_and_cx[(tense_and_cx > 0).any(1)] # prune null counts
    nom_yiq_dataset[cx] = tense_and_cx
```

```python
fig = plt.figure(figsize=(10,10))

for i, data in enumerate(nom_yiq_dataset.items()):

    # axes + linear regression analysis
    cx, df = data
    x, y =  df[cx], df['yqtl']
    slope, intercept, r, p, std_err = linregress(x,y)

    # make plot
    ax = fig.add_subplot(3, 2, i+1)
    ax.scatter(x, y, color='', edgecolor='black')
    ax.set_ylabel('yiqtol')
    ax.set_xlabel(f'{cx}')
    ax.set_title(f'{cx} (r={round(r, 2)}) (p={round(p, 3)})')

    # plot regression line
    rx = np.unique(x.values)
    ry = slope*rx+intercept
    ax.plot(rx, ry, 'red', linewidth=1)

fig.tight_layout()
plt.savefig(fg_path('yqtl_nominalizers_scatter.png'), dpi=300, bbox_inches='tight')
plt.show()
```

The graphs show the strongest effect in the `bare` category ($p=0.002$). This is essentially a reproduction of the principle component analysis results, which was based on these same features together.

The graph is likewise very similar to the yiqtol and PC1 plot above. We do not see nearly as much as an effect with any of the other time words.

## Collocation with Nominalizing Constructions

Up to this point we have compared the behavior of time words against their co-occurrence with yiqtol. We've also shown that "adverb" words which are distinguished by a preference for bare constructions are likewise distinguished by their preference for yiqtol.

Up until now we have been able to find any strong effects when yiqtol is paired with the nominalizing constructions themselves. So now we will attempt to measure this directly. We ask: **Is the yiqtol more or less likely to occur in the presence of a nominalizing construction?** Remember that for our purposes here we define a "nominalizing construction" as any construction which communicates definiteness, quantity, or posession.

To answer the research question, we first compose tables of collocations between all verb tenses and these various constructions. We build two datasets:

1. yiqtol collocations with nominalizers for all time words
2. yiqtol collocations with nominalizers for noun words

We make the second table to see whether yiqtol has any suppressive effect on nominalizers even amongst noun-like terms.

## Fisher's

```python
noun_data = times[times.time.isin(nouns.index)]
```

```python
# compile collocation table of time heads with nominalizing constructions
noms = [
    'time_pl', 'quantified', 'definite',
    'demonstrative', 'ordinal', 'time_sffx',
    'bare', 'genitive'
]
exclude_verbs = ['infa','infc','impv']

verb_nom_cols = pd.pivot_table(
    times,
    index=['tense'],
    values=noms,
    aggfunc=np.sum
).drop(exclude_verbs)

verb_nom_ratios = verb_nom_cols.div(verb_nom_cols.sum(1), axis=0)

verb_nom_cols_nouns = pd.pivot_table(
    noun_data,
    index=['tense'],
    values=noms,
    aggfunc=np.sum
).drop(exclude_verbs)

verb_nom_ratios_nouns = verb_nom_cols_nouns.div(verb_nom_cols_nouns.sum(1), axis=0)
```

```python
verb_nom_cols
```

      bare
      definite
      demonstrative
      genitive
      ordinal
      quantified
      time_pl
      time_sffx

      tense

      ptcp
      70.0
      95.0
      16.0
      28.0
      4.0
      18.0
      22.0
      10.0

      qtl
      230.0
      274.0
      130.0
      108.0
      21.0
      162.0
      169.0
      52.0

      wqtl
      45.0
      98.0
      38.0
      21.0
      16.0
      48.0
      44.0
      5.0

      wyqtl
      136.0
      315.0
      163.0
      57.0
      40.0
      165.0
      134.0
      8.0

      yqtl
      579.0
      275.0
      98.0
      120.0
      35.0
      155.0
      156.0
      43.0

We also have calculated ratios across the verbs:

```python
verb_nom_ratios
```

      bare
      definite
      demonstrative
      genitive
      ordinal
      quantified
      time_pl
      time_sffx

      tense

      ptcp
      0.266160
      0.361217
      0.060837
      0.106464
      0.015209
      0.068441
      0.083650
      0.038023

      qtl
      0.200698
      0.239092
      0.113438
      0.094241
      0.018325
      0.141361
      0.147469
      0.045375

      wqtl
      0.142857
      0.311111
      0.120635
      0.066667
      0.050794
      0.152381
      0.139683
      0.015873

      wyqtl
      0.133595
      0.309430
      0.160118
      0.055992
      0.039293
      0.162083
      0.131631
      0.007859

      yqtl
      0.396304
      0.188227
      0.067077
      0.082136
      0.023956
      0.106092
      0.106776
      0.029432

And now the nouns raw counts along with those ratios:

```python
verb_nom_cols_nouns
```

      bare
      definite
      demonstrative
      genitive
      ordinal
      quantified
      time_pl
      time_sffx

      tense

      ptcp
      6.0
      92.0
      16.0
      22.0
      4.0
      18.0
      20.0
      7.0

      qtl
      9.0
      266.0
      128.0
      97.0
      21.0
      160.0
      146.0
      31.0

      wqtl
      2.0
      98.0
      38.0
      20.0
      16.0
      48.0
      44.0
      5.0

      wyqtl
      21.0
      307.0
      163.0
      48.0
      39.0
      164.0
      128.0
      2.0

      yqtl
      14.0
      267.0
      98.0
      112.0
      35.0
      152.0
      145.0
      30.0

```python
verb_nom_cols_nouns.sum(1)
```

    tense
    ptcp     185.0
    qtl      858.0
    wqtl     271.0
    wyqtl    872.0
    yqtl     853.0
    dtype: float64

```python
verb_nom_ratios_nouns
```

      bare
      definite
      demonstrative
      genitive
      ordinal
      quantified
      time_pl
      time_sffx

      tense

      ptcp
      0.032432
      0.497297
      0.086486
      0.118919
      0.021622
      0.097297
      0.108108
      0.037838

      qtl
      0.010490
      0.310023
      0.149184
      0.113054
      0.024476
      0.186480
      0.170163
      0.036131

      wqtl
      0.007380
      0.361624
      0.140221
      0.073801
      0.059041
      0.177122
      0.162362
      0.018450

      wyqtl
      0.024083
      0.352064
      0.186927
      0.055046
      0.044725
      0.188073
      0.146789
      0.002294

      yqtl
      0.016413
      0.313013
      0.114889
      0.131301
      0.041032
      0.178195
      0.169988
      0.035170

#### Fisher's with All

```python
verb_nom_cols_fishers, verb_nom_cols_odds = apply_fishers(verb_nom_cols, 0, 1)
```

```python
plt.figure(figsize=(10, 6))
ax = sns.heatmap(verb_nom_cols_fishers, center=0, robust=True)
ax.set_yticklabels(verb_nom_cols_fishers.index, rotation=0)
plt.savefig(fg_path('tense_nominalizer_attractions.png'), dpi=300, bbox_inches='tight')
plt.title('Tense and Nominalization CX Attractions (Fisher\'s)')
plt.show()
display(verb_nom_cols_fishers)
```

#### Fisher's with Nouns

```python
verb_nom_cols_noun_fishers, verb_nom_cols_noun_fishers_odds = apply_fishers(verb_nom_cols_nouns, 0, 1)
```

```python
plt.figure(figsize=(10, 6))
ax = sns.heatmap(verb_nom_cols_noun_fishers, center=0, robust=True)
ax.set_yticklabels(verb_nom_cols_noun_fishers.index, rotation=0)
plt.savefig(fg_path('tense_nominalizer_attractions_nouns.png'), dpi=300, bbox_inches='tight')
plt.title('Tense and Nominalization CX Attractions with Nouns (Fisher\'s)')
plt.show()
display(verb_nom_cols_noun_fishers)
```

Looking at the associations for yqtl, we can make note of two collocations which are statistically significant $(-1.3 > attraction > 1.3)$:

* yiqtol with a genitive (i.e. construct) relation on the time word ($a=3.6$)
* yiqtol with a pronominal suffix on the time word ($a=1.6$)

We also can see a slight skew away from collocating with definiteness ($a=-1.21$), which falls just shy of the significance threshold. The demonstrative relation, however, is strongly disassociated with $a=-2.6$

Interestingly, yiqtol shares this quality with qatal, which exhibits significant association for the suffix ($a=1.9$) and higher association, though not meeting the significance threshold, with the genitive ($a=1.04$).

Looking at the raw counts again, we can get a sense for the magnitude of the collocations:

```python
verb_nom_cols_nouns
```

      bare
      definite
      demonstrative
      genitive
      ordinal
      quantified
      time_pl
      time_sffx

      tense

      ptcp
      6.0
      92.0
      16.0
      22.0
      4.0
      18.0
      20.0
      7.0

      qtl
      9.0
      266.0
      128.0
      97.0
      21.0
      160.0
      146.0
      31.0

      wqtl
      2.0
      98.0
      38.0
      20.0
      16.0
      48.0
      44.0
      5.0

      wyqtl
      21.0
      307.0
      163.0
      48.0
      39.0
      164.0
      128.0
      2.0

      yqtl
      14.0
      267.0
      98.0
      112.0
      35.0
      152.0
      145.0
      30.0

Starting with the genitive: it has a frequency of $112$ when combined with yiqtol and $97$ with qatal, but only $48$ with wayyiqtol, $20$ with weqatal, and $22$ with participle.

The suffix, which has fewer occurrences, appears with yiqtol $30$ times, qatal $31$ times, but with wayyiqtol only $2$, weqetal $5$ and the participle $7$.

**One hypothesis for the higher frequency of suffix with yiqtol is that it is caused by the yiqtol's more frequent use in the domain of discourse**. Within such contexts, suffixes, especially in second person, might be higher in general.

## ΔP Asymmetrical Tests

The Fisher's Exact test is symmetrical, meaning that the association between two variables is the same for each one. However, an asymmetrical significance test can reveal dependency of one variable on the other. To give a simple real-world example: the noun "blood" is likely to cue a word such as "red"; but "red" is likely to cue any number of nouns besides "blood". This shows that the co-occurrence relationship between the two forms is not symmetrical.

Following Ellis 2006, we can apply an assymetrical significance test to language data on the grounds the one construction would hypothetically serve as a cue, and the other as the response.

> $\begin{align}
\Delta P = P(O|C) - P(O|-C)\\
=\frac{a}{a+b} - \frac{c}{c+d}\\
=\frac{ad-bc}{(a+b)(c+d)}
\end{align}$

> "􏰁$\Delta P$ is the probability of the outcome given the cue $P(O|C)$ minus the probability of the outcome in the absence of the cue $P(O|–C)$. When these are the same, when the outcome is just as likely when the cue is present as when it is not, there is no covariation between the two events and 􏰁$P 1⁄4 0$. $􏰁P$ approaches $1.0$ as the presence of the cue increases the likelihood of the outcome and approaches $–1.0$ as the cue decreases the chance of the outcome—a negative association." -Ellis, "Language Acquisition," 2006, 11.

For our purposes here, we will model the time adverbial as the cue and the verb as the response. We want to answer **does an asymmetrical dependency between "adverbs" and yiqtol exist?**

#### Time as cue

Given a time, what is the likelihood that we observe a particular tense?

```python
# unidirectional ΔP test
a,b,c,d,e = contingency_table(tense_cols, 0, 1) # get contingencies

time_cue = a / (a + b) - c / (c + d)
```

We look at the first 10 values.

```python
time_cue.iloc[:10,:]
```

      tense
      qtl
      wqtl
      wyqtl
      yqtl
      ptcp

      time

      יום
      0.038117
      0.032080
      0.026754
      -0.114814
      0.017863

      עוד
      -0.152609
      -0.047727
      -0.077087
      0.272182
      0.005241

      שׁנה
      0.202009
      -0.017236
      0.097868
      -0.222135
      -0.060505

      עולם
      -0.100486
      0.077577
      -0.182228
      0.208435
      -0.003297

      עת
      -0.027073
      -0.018394
      0.041062
      -0.026511
      0.030915

      אז
      0.097532
      -0.070442
      -0.219152
      0.239020
      -0.046958

      לילה
      -0.048169
      -0.030243
      0.301096
      -0.249445
      0.026762

      עתה
      0.233904
      -0.069767
      -0.225718
      0.111423
      -0.049841

      בקר
      -0.230085
      0.010427
      0.312650
      -0.077778
      -0.015214

      ערב
      -0.158723
      0.219190
      -0.036670
      0.019366
      -0.043163

#### Verb as cue

Another flavor of the $\Delta P$ looks from the other direction, with the feature as the cue, in this case the verb tense. We calculate that below. To aid the comparison, we flip the axis so that the row always contains the cue.

```python
# unidirectional ΔP test

verb_cue = (a / (a + c) - b / (b + d)).T
```

We look at the first 10 values.

```python
verb_cue.iloc[:,:10]
```

      time
      יום
      עוד
      שׁנה
      עולם
      עת
      אז
      לילה
      עתה
      בקר
      ערב

      tense

      qtl
      0.044867
      -0.073861
      0.067662
      -0.021771
      -0.005278
      0.018863
      -0.008184
      0.034929
      -0.033993
      -0.018619

      wqtl
      0.117333
      -0.071777
      -0.017939
      0.052227
      -0.011143
      -0.042334
      -0.015967
      -0.032373
      0.004787
      0.079896

      wyqtl
      0.036103
      -0.042772
      0.037580
      -0.045262
      0.009178
      -0.048591
      0.058647
      -0.038641
      0.052953
      -0.004931

      yqtl
      -0.112975
      0.110121
      -0.062197
      0.037750
      -0.004321
      0.038644
      -0.035429
      0.013909
      -0.009606
      0.001899

      ptcp
      0.063051
      0.007606
      -0.060769
      -0.002142
      0.018074
      -0.027233
      0.013634
      -0.022318
      -0.006740
      -0.015183

Inspecting the some of the values, we can see that the time adverbial seems to be more predictive of the verb than the verb is of the time adverbial. Here's an example with עוד.

### עוד as an example

We see that with time as the cue, the time עוד has a pretty strong score with yiqtol:

```python
time_cue.loc['עוד']
```

    tense
    qtl     -0.152609
    wqtl    -0.047727
    wyqtl   -0.077087
    yqtl     0.272182
    ptcp     0.005241
    Name: עוד, dtype: float64

This relationship is not completely symmetrical. The verb is a poorer predictor of עוד:

```python
verb_cue.loc['yqtl']['עוד']
```

    0.11012143241838356

Why is this the case? We can see it more clearly if we look at the ratios of yqtl representation in עוד versus the ratio across yqtl.

```python
tense_cols['yqtl']['עוד'] / tense_cols['yqtl'].sum()
```

    0.17491166077738515

```python
tense_cols.loc['עוד']['yqtl'] / tense_cols.loc['עוד'].sum()
```

    0.61875

עוד makes up $17\%$ of yiqtol's total. But yiqtol makes up $62\%$ of עוד's total. **The numbers suggest that עוד is more predictive of yiqtol than yiqtol is of עוד.**

### The overall predictive power of time for verb

We can get a sense for the overall predictive power for each test based on the cumulative mean of its absolute values.

Below we retrieve the mean association score accross the absolute value for the time as a cue.

```python
time_cue.abs().mean().mean()
```

    0.21081859098659791

And next we see the same value for the verb.

```python
verb_cue.abs().mean().mean()
```

    0.005877288199936625

**Based on this preliminary data, we can conclude that in general, the time adverbial is more predictive of a tense than a tense is for a given adverbial.** Intuitively this makes sense. There are far more time adverbials that can be chosen at any given moment, but there are far less verb tense choices.

### TODO: Add comparisons between nouns/adverbs, see what these results mean for the issue of yiqtol/adverb dependency

## Manual Investigation

We export a series of spreadsheets for manually inspecting collocations.

### Co-occurrences "Adverbs" and yiqtols

```python
advb_instances = times[(times.time.isin(advbs.index)) & (times.tense == 'yqtl')]

advb_inspect = advb_instances[['time', 'ref', 'clause', ]].sort_values(by=['time','node'])

advb_inspect.shape
```

    (550, 3)

```python
advb_inspect.to_csv('../tables/inspect/yqtl_adverbs.tsv', encoding='utf-16', sep='\t')
```

### Co-occurrences of "nouns" and yiqtols

```python
noun_instances = times[(times.time.isin(nouns.index)) & (times.tense == 'yqtl')]

noun_inspect = noun_instances[['time', 'ref', 'clause', ]].sort_values(by=['time','node'])

noun_inspect.shape
```

    (526, 3)

```python
noun_inspect.to_csv('../tables/inspect/yqtl_nouns.tsv', encoding='utf-16', sep='\t')
```


# Statistical constraints on observing low-angle normal fault seismicity

## Introduction

Low-angle normal faults (LANFs), with dips less than 30$^\circ$, are
well described in the geologic record. They are hypothesized to have
an important role in accommodating large-magnitude continental
extension \citep{howard1987crustal} and crustal thinning
\citep{lister1986detachment}, and their recognition has been one of
the most important developments in tectonics over the past several
decades \citep{wernicke2009detachment}. However, despite widespread
field observations of inactive LANFs, and their central role in modern
extensional tectonic theory, they remain enigmatic and contentious
structures, and it is not clear if they are seismically active at low dip
angles in the upper crust. This is for two reasons: because brittle faulting
on LANFs is in apparent conflict with standard Andersonian rock mechanical
theory as typically applied to the upper crust
\citep{axen2004lanfmech}, and because observations of active faulting
on LANFs is sparse and at times ambiguous \citep{wernicke1995seis}. A
considerable amount of research has been performed to address the
former concern, reconciling LANF slip with rock mechanics \citep [e.g.,]
[]{axenbartley1997, collettini2011lanfmech}. The latter issue, the paucity of
definitive LANF earthquakes in focal mechanism catalogs, has inhibited
hypothesis testing of LANF fault theory, and has also contributed to a mode
of thought where the  absence of evidence for LANF seismicity is taken
as evidence that LANFs are inactive or aseismic \citep{jackson1987,
collettinisibson2001}. However, the lack of observed seismic slip on
continental LANFs may be simply due to the rarity of earthquakes on
LANFs, coupled with the small number of potential active structures.

In this work, we choose to directly address the question of whether
the lack of observed large earthquakes on LANFs may be
interpreted as an indication that
LANFs do not slip seismically, or if it is better explained as an
effect of the small number of active seismogenic LANFs with low moment
accumulation rates (and hence long recurrence intervals).
We then calculate the maximum likelihood of observing a significant
earthquake on a LANF by treating all potentially active LANFs described in the
literature as seismically active at their surface dip angles
throughout the upper crust. %, and displaying typical seismic behavior.
Under these assumptions, we create synthetic earthquake catalogs with
Gutenberg-Richter and `characteristic' frequency-magnitude distributions
based on each fault's geometry and slip rate, and then we calculate the
probability of observing earthquakes on each, and any, fault over different
durations of observation.

### LANF Slip, Mohr-Coulomb Failure Theory, and Seismicity

Areas of the crust undergoing active extension are generally assumed
to have a subvertical maximum compressive stress.  Mohr-Coulomb
theory, as applied to the crust, predicts that a fault with a typical
coefficient of friction for rocks (0.6--0.8) should lock up if it is
oriented at an angle greater than 60$^\circ$ to the maximum compressive stress,
and new, optimally oriented faults should form \citep{sibson1985}.  Therefore,
for normal faults with dips less than 30$^\circ$, either much lower
fault friction or elevated pore fluid pressure is required for fault slip.

Evidence for seismic slip on LANFs is sparse.  This is partly due to the
ambiguity of the rupture plane in earthquake focal mechanisms, which have
nodal planes 90$^\circ$ apart, and therefore have a
high-angle nodal plane if they have a low-angle nodal plane.  Without
ancillary information indicating which plane slipped, searches of earthquake
catalogs cannot yield unique results as to whether they contain LANF events.
Several collections of normal fault earthquakes
with known surface breaks \citep{jackson1987, collettinisibson2001}, thereby
resolving dip ambiguity, contain no low-angle events, though the collections
are small ($\le$ 25 events).  Some candidate events exist, but they are
undersea \citep[e.g.,][]{abers2001} or difficult to verify \citep[e.g.,]
[]{doser1987ancash}.

## Potentiall Active LANFs

Over the past decade or so, many field studies have found evidence for LANF
activity in orogens throughout the world. These studies typically find arrays of
Quaternary normal fault scarps on the fault traces and/or in the hanging walls
of mapped or inferred low-angle detachment faults \citep [e.g.,]
[]{axen1999baja}. Some studies also have bedrock thermochronology data from the
exhumed footwalls of the detachments that is suggestive of ongoing rapid
exhumation \citep [e.g.,][]{sundell2013lunggar}, although this data does not
preclude a recent cessation of faulting. In some cases, additional evidence for
LANF activity comes from geophysical data such as GPS geodesy \citep [e.g.,]
[]{hreinsdottir2009altotib} and seismic waves \citep [e.g.,][]{doser1987ancash}.

We have compiled all potentially active LANFs with known subareal
fault traces from a thorough review of the literature, finding twenty
total (Figure~\ref{fig:lanf_map}).  We have then mapped the approximate fault
traces into a GIS file (available at https://github.com/cossatot/LANF\_gis),
with metadata such as slip rate and source. Though the fault traces of many
LANFs considered here are obscured by vegetation or agriculture, others
display large fault scarps in Quaternary sediments, particularly those in the
dry climates of Tibet \citep[e.g.,][]{styron2013slr, kapp2005nqtl} and the
western US \citep[e.g.,][]{axen1999baja, hayman2003dv}, which are commonly
interpreted as evidence for past seismic slip.  About half are in Tibet,
consistent with hypotheses that LANFs and metamorphic core complexes
form in areas of hot, thick crust \citep [e.g.,][]{buck1991mcc}.  The
rest are distributed through other areas of active continental
extension: the North American Basin and Range, the Malay Archipelago,
western Turkey, Italy, and Peru. Several of the most-commonly cited
candidates for seismically active LANFs were not included because they
do not have a clearly-defined, mappable fault trace, which is
necessary for our earthquake likelihood calculations.  These include the
submarine core complexes in the Woodlark Basin \citep{abers2001}, the fault
responsible for the 1995 Aigion, Greece earthquake \citep{bernard1997}
and other potential LANFs underneath the Gulf of Corinth, and the
fault responsible for the 1952 Ancash, Peru earthquake
\citep{doser1987ancash}.

```
# show map and figure caption
```

## Likelihood of observing a LANF event
### Earthquake likelihood on an individual LANF

To estimate the likelihood of observing a significant earthquake on an
individual LANF over some contiguous time window of length $t$ (in
years), we perform a Monte Carlo simulation in which we create 4000
synthetic time series of earthquakes, with unique values for fault
geometry and slip rate for each time series. Then, for each time
series we calculate the fraction of unique time windows of length $t$
in which an earthquake as large or larger than a given magnitude
occurs.  We take this value as the probability of observing an
earthquake greater than or equal to moment magnitude $M$ over
time period $t$, which we will refer to in general as $P(M,t)$.

The geometry for each fault is estimated based on the length of the
fault trace, the dip of the fault, and the estimated seismogenic
thickness or fault locking depth in the area.  The fault is treated as
planar for simplicity of calculations, even though the exposed
footwalls of many detachment faults are highly corrugated.  We
determine the fault length by measuring the approximate length of the
mapped fault trace perpendicular to the assumed extension direction;
for faults that change dip significantly along strike (e.g., the Dixie
Valley fault), we only consider the low-angle segments of the fault.
Values for the dip are taken from the literature in most cases, and
measured of the dip of footwall triangular facets (interpreted as the exhumed
fault plane) from SRTM data otherwise. In all cases, ranges of fault
geometries are considered, encompassing the degree to which the values are
known. The fault locking depth is assumed to be 10 km in the absence of other
evidence (such as a geodetic study, \citep[e.g.,][]{hreinsdottir2009altotib}).

Slip rates of the 20 LANFs are similarly gathered from the
literature if possible, or given broad ranges if not documented
(e.g., 1--10 mm yr$^{-1}$).  In the Monte Carlo simulation, samples
for slip rate and dip are drawn from uniform distributions defined
by the maximum and minimum values.  Based on field observations,
some faults have dip ranges that go above 30$^\circ$; this study
only considers slip on faults shallower than this, so for these
faults, dip values are sampled from the minimum to 30$^\circ$ and
the resulting probabilities are then multiplied by the fraction of
the dip range that is under 30$^\circ$.

Each earthquake synthetic earthquake sequence is generated by
randomly sampling either 50,000 events from a tapered Gutenberg-Richter (GR)
distribution with corner magnitude $M_c = 7.64$ and $\beta = 0.65$
(from values estimated by \citet{birdkagan2004f_m} for continental
rifts), or a 25,000 events from `characteristic' distribution. It is not
certain which distribution more appropriately describes seismicity on a single
LANF, though studies of many individual fault rupture histories suggests
that the characteristic distribution is more accurate \citep{hecker2013eqdist}.
The smaller number of samples from the characteristic distribution is due to
the increased computation time associated with a higher proportion of large
events.  The samples are taken
from an interval $M = [5.0, \, M_{max}]$, where $M_{max}$ is calculated as the
moment magnitude $M$ resulting from fault slip $D$ = 15 m over a fault of
length $L$ cutting through a seismogenic thickness $z$ at dip $\delta$,
given the relations

\begin{equation}
 M_o = \mu L z D \,/ \, \sin \delta
 \end{equation}

and

\begin{equation}
M = 2/3 \; \log_{10} (M_o) - 6
\end{equation}

where $\mu = 30$ GPa is the shear modulus and $M_o$ is the seismic
moment in N m \citep{kagan2003pepi}.  The characteristic distribution has a
large-magnitude mode corresponding to $D$ = 1.5 m on the fault, a typical
slip distance for normal fault events \citep[e.g.][]{wesnousky2008displacement}.
The distributions are shown in Figure~\ref{fig:fms}.

These calculations rely on two important assumptions that warrant some
discussion.  The first is that each earthquake ruptures the entire
fault patch uniformly.  Though this is highly improbable fault behavior,
the long-term statistical distribution of earthquake recurrence is
insensitive to assumptions about slip distribution in individual events:
if $n$ different, equal fault patches rupture independently, each
requires $n$ times the interseismic strain accumulation time to rupture with
some earthquake of magnitude $M$ versus a single fault rupturing uniformly;
but on the whole, magnitude $M$ events would happen with the same long-term
frequency.  The next assumption is that earthquakes are ordered randomly and
separated by the time necessary for sufficient strain to accumulate for each
earthquake to occur.  This means that foreshock and aftershock sequences
and other types of event clustering are not taken into account.  However,
the modal inter-event times for earthquakes $\ge M \,6$ or so are greater than
a hundred years for many LANFs [supplemental figure xx], so the ordering of
events does not impact the results, as this is longer than our maximum
observation window.  Furthermore, any clustering resulting in
event spacing less than the observation window would decrease $P(M,t)$, and
we are concerned with calculating the maximum $P(M,t)$.

### Setting up the problem

#### Import necessary modules

```
#%pylab inline
#%config InlineBackend.figure_format = 'retina'
```

```
from matplotlib.pyplot import *
```

```
import sys
sys.path.append('../eq_stats')

import numpy as np
import pandas as pd
import eq_stats as eqs
import time
from joblib import Parallel, delayed
from itertools import chain
```

#### Read in fault data table

Makes a Pandas dataframe of fault data (length, slip rates, etc.)

```
f = pd.read_csv('../data/lanf_stats.csv', index_col=0)
```

#### Define some variables to be used later

```
n_cores = -3
n_eq_samp = 3e4 # number of earthquakes in time series
time_window = np.hstack( (1, np.arange(5, 105, step=5) ) ) # observation times
mc_iters = 2e3 # number of Monte Carlo iterations
mc_index = np.arange(mc_iters, dtype='int')
mc_cols = ['dip', 'Ddot'] + [t for t in time_window]
max_eq_slip = 15 #m
char_eq_slip= 1.5 #m
Mc = 7.64 # Hypothetical corner magnitude for Continental Rift Boundaries
```

Make list of minimum search magnitude $M_{min}$, and then make MultiIndex for Pandas dataframes

```
min_M_list = [5, 5.5, 6, 6.5, 7, 7.5]

df_ind_tuples = [[i, M] for i in mc_index for M in min_M_list]
df_multi_ind = pd.MultiIndex.from_tuples(df_ind_tuples, names=['mc_iter','M'])

rec_int_bins = np.logspace(1, 5)
```

### Calculate $P(M,t)$ for Gutenberg-Richter frequency-magnitude distributions

#### Define a function for Joblib Parallel to calculate probabilities for each iteration.

Function is defined here so it can access all variables generated by script, not just passed variables.  This makes the code cleaner even if it's not very abstracted.

Here is what this function does:

- Get the dip, Ddot and maximum earthquake magnitude for each iteration.
- Take this info and make the earthquake sequence:
    - Take the max earthquake magnitude and make a frequency-magnitude distribution based on a Gutenburg-Richter exponential model.
    - Take 50k samples from this distribution,
- Make an earthquake time series form the EQ sequence
    - Calculate the interseismic strain accumulation time for each event
    - Separate each earthquake in the sequence with the appropriate number of years with no events.
- Calculate the probability of observation
    - Run a rolling maximum for each $t$ in [1, 5, 10, 15, ..., 95, 100]
    - Calculate the observation probability above $M_{min}$ in [5, 5.5, 6, 6.5, 7, 7.5]
- Calculate inter-event times for EQs $\ge \, M$

```
def calc_iter_probs(iter):
    df_iter = fdf.loc[iter].copy()
    df_iter['dip'] = mc_d['dip_samp'][iter]
    df_iter['Ddot'] = mc_d['Ddot_samp'][iter]

    # Generate EQ sample/sequence from F(M) dist.
    m_vec = np.linspace(5, mc_d['max_M'][iter], num=1000)
    fm_vec = eqs.F(m_vec, Mc=Mc)
    M_samp = eqs.sample_from_pdf(m_vec, fm_vec, n_eq_samp)
    Mo_samp = eqs.calc_Mo_from_M(M_samp)

    # Make time series of earthquakes, including no eq years
    recur_int = eqs.calc_recurrence_interval(Mo=Mo_samp,
                                             dip=mc_d['dip_samp'][iter],
                                             slip_rate=mc_d['Ddot_samp'][iter],
                                             L=params['L_km'],
                                             z=params['z_km'])

    cum_yrs = eqs.calc_cumulative_yrs(recur_int)
    eq_series = eqs.make_eq_time_series(M_samp, cum_yrs)

    # calculate probability of observing EQ in time_window
    for t in time_window:
        roll_max = pd.rolling_max(eq_series, t)
        df_iter[t] = (eqs.get_probability_above_value(roll_max, min_M_list)
                      * mc_d['dip_frac'])

    # calculate histgrams of recurrence intervals
    rec_int_counts_df = rec_int_df.loc[iter].copy()
    for mm in np.array(min_M_list):
        ints = np.diff( np.where(eq_series >= mm) )
        rec_int_counts_df.loc[mm] = np.histogram(ints, bins=rec_int_bins)[0]
```

#### Iterate through the faults in the fault database, doing all the calculations for each.

The setup of this for loop is basically this:

- Make DataFrame for each fault.
    - Columns are dip, Ddot, and observation time windows.
    - Rows are values for each Monte Carlo iteration.  Values for time windows are calculated probabilities.

- Calculate maximum earthquake magnitude for each MC iteration.

- Run the above 'calc_iter_probs' function (parallelized over the MC iterations) and concatenate the results

### Note! Not actually running the calcs here, as it's a looong process!

```
for fault in list(f.index):
    fdf = pd.DataFrame(index=df_multi_ind, columns=mc_cols, dtype='float')
    params = f.loc[fault]
    mc_d = {}
    mc_d['dip_samp'], mc_d['dip_frac'] = eqs.dip_rand_samp( params['dip_deg'],
                                                         params['dip_err_deg'],
                                                         mc_iters)

    mc_d['Ddot_samp'] = eqs.Ddot_rand_samp(params['slip_rate_mm_a'],
                                           params['sr_err_mm_a'], mc_iters)

    mc_d['max_Mo'] = eqs.calc_Mo_from_fault_params(L=params['L_km'],
                                                   z=params['z_km'],
                                                   dip=mc_d['dip_samp'],
                                                   D=max_eq_slip)

    mc_d['max_M'] = eqs.calc_M_from_Mo(mc_d['max_Mo'])

    rec_int_df = pd.DataFrame(columns = rec_int_bins[1:],
                              index=df_multi_ind, dtype='float')
    t0 = time.time()
    out_list = Parallel(n_jobs=-3)( delayed( calc_iter_probs)(ii)
                                    for ii in mc_index)
    print 'done with', fault, 'parallel calcs in {} s'.format((time.time()-t0))
    for ii in mc_index:
        fdf.loc[ii][:] = out_list[ii][0]
        rec_int_df.loc[ii][:] = out_list[ii][1]

    fdf.to_csv('../results/{}_test.csv'.format(fault))
```

    done with kongur_shan parallel calcs in 51.947163105 s
    done with leo_pargil parallel calcs in 418.76543808 s
    done with gurla_mandhata parallel calcs in 113.975022078 s
    done with s_lunggar parallel calcs in 241.274301052 s
    done with n_lunggar parallel calcs in 259.45785284 s
    done with pqx_qingdu parallel calcs in 836.985168934 s
    done with pqx_n parallel calcs in 917.926791191 s
    done with nqtl parallel calcs in 113.513681889 s
    done with alto_tiberina parallel calcs in 233.532536983 s
    done with death_valley parallel calcs in 201.206351042 s
    done with panamint_valley parallel calcs in 102.183755159 s
    done with laguna_salada parallel calcs in 217.666782141 s
    done with dixie_valley parallel calcs in 1027.02227688 s
    done with dayman_dome parallel calcs in 103.56942296 s
    done with papangeo parallel calcs in 183.855463028 s
    done with tokorondo parallel calcs in 78.5454099178 s
    done with cordillera_blanca parallel calcs in 66.0141530037 s
    done with kuzey parallel calcs in 268.01406312 s
    done with guney parallel calcs in 158.371592999 s

### Calculate $P(M,t)$ for faults with characteristic frequency-magnitude distributions

```
def calc_iter_probs(ii):
    df_iter = fdf.loc[ii].copy()
    df_iter['dip'] = mc_d['dip_samp'][ii]
    df_iter['Ddot'] = mc_d['Ddot_samp'][ii]

    # Generate EQ sample/sequence from F(M) dist.
    m_vec = np.linspace(5, mc_d['max_M'][ii], num=1000)
    fm_vec = eqs.F_char(m_vec, Mc=Mc, char_M=mc_d['char_M'][ii])
    M_samp = eqs.sample_from_pdf(m_vec, fm_vec, n_eq_samp)
    Mo_samp = eqs.calc_Mo_from_M(M_samp)

    # Make time series of earthquakes, including no eq years
    recur_int = eqs.calc_recurrence_interval(Mo=Mo_samp,
                                             dip=mc_d['dip_samp'][ii],
                                             slip_rate=mc_d['Ddot_samp'][ii],
                                             L=params['L_km'],
                                             z=params['z_km'])

    cum_yrs = eqs.calc_cumulative_yrs(recur_int)
    eq_series = eqs.make_eq_time_series(M_samp, cum_yrs)

    # calculate probability of observing EQ in time_window
    for t in time_window:
        roll_max = pd.rolling_max(eq_series, t)
        df_iter[t] = (eqs.get_probability_above_value(roll_max, min_M_list)
                      * mc_d['dip_frac'])

    # calculate histgrams of recurrence intervals
    rec_int_counts_df = rec_int_df.loc[ii].copy()
    for mm in np.array(min_M_list):
        ints = np.diff( np.where(eq_series >= mm) )
```

```
for fault in list(f.index):
    fdf = pd.DataFrame(index=df_multi_ind, columns=mc_cols, dtype='float')
    params = f.loc[fault]
    mc_d = {}
    mc_d['dip_samp'], mc_d['dip_frac'] = eqs.dip_rand_samp( params['dip_deg'],
                                                         params['dip_err_deg'],
                                                         mc_iters)

    mc_d['Ddot_samp'] = eqs.Ddot_rand_samp(params['slip_rate_mm_a'],
                                           params['sr_err_mm_a'], mc_iters)

    mc_d['max_Mo'] = eqs.calc_Mo_from_fault_params(L=params['L_km'],
                                                   z=params['z_km'],
                                                   dip=mc_d['dip_samp'],
                                                   D=max_eq_slip)

    mc_d['max_M'] = eqs.calc_M_from_Mo(mc_d['max_Mo'])

    mc_d['char_Mo'] = eqs.calc_Mo_from_fault_params(L=params['L_km'],
                                                    z=params['z_km'],
                                                    dip=mc_d['dip_samp'],
                                                    D=char_eq_slip)

    mc_d['char_M'] = eqs.calc_M_from_Mo(mc_d['char_Mo'])

    rec_int_df = pd.DataFrame(columns = rec_int_bins[1:],
                              index=df_multi_ind, dtype='float')
    t0 = time.time()
    out_list = Parallel(n_jobs=n_cores)( delayed( calc_iter_probs)(ii)
                                    for ii in mc_index)
    print 'done with', fault, 'parallel calcs in {} s'.format((time.time()-t0))
    for ii in mc_index:
        fdf.loc[ii][:] = out_list[ii][0]
        rec_int_df.loc[ii][:] = out_list[ii][1]

    fdf.to_csv('../results/{}_char.csv'.format(fault))
```

### Examining individual fault results

#### Load datasets into Pandas dataframes

test with one:

```
pv = pd.read_csv('../results/panamint_valley_gr.csv', index_col=[0])
pv_c = pd.read_csv('../results/panamint_valley_char.csv', index_col=[0])
```

```
tw_xarray = np.tile(time_window, (mc_iters,1))

tw_xarray.shape
```

    (2000, 21)

```
tw_cols = list(time_window.astype('str'))
```

make some plotting functions

```
def plot_probs(df, lw=0.5, alpha=0.2):

    df_5 = df[df.M==5]
    df_55 = df[df.M==5.5]
    df_6 = df[df.M==6]
    df_65 = df[df.M==6.5]
    df_7 = df[df.M==7]
    df_75 = df[df.M==7.5]

    aa = plot(tw_xarray.T, df_5[tw_cols].T, 'b',
              lw=lw, alpha=alpha)
    bb = plot(tw_xarray.T, df_55[tw_cols].T, 'c',
              lw=lw, alpha=alpha)
    cc = plot(tw_xarray.T, df_6[tw_cols].T, 'g',
              lw=lw, alpha=alpha)
    dd = plot(tw_xarray.T, df_65[tw_cols].T, 'gold',
              lw=lw, alpha=alpha)
    ee = plot(tw_xarray.T, df_7[tw_cols].T, 'orange',
              lw=lw, alpha=alpha)
    ff = plot(tw_xarray.T, df_75[tw_cols].T, 'r',
              lw=lw, alpha=alpha)

    return aa, bb, cc, dd, ee, ff
```

```
def pmf_x_secs(df, tw, n_bins=101, hist_type='stepfilled', alpha=0.5):
    hist_bins = np.linspace(0, 1, num=n_bins)
    tw = str(tw)

    aa = df[df.M==5][tw].hist(bins=hist_bins, histtype=hist_type,
                              color='b', alpha=alpha)
    bb = df[df.M==5.5][tw].hist(bins=hist_bins, histtype=hist_type,
                                color='c', alpha=alpha)
    cc = df[df.M==6][tw].hist(bins=hist_bins, histtype=hist_type,
                              color='g', alpha=alpha)
    dd = df[df.M==6.5][tw].hist(bins=hist_bins, histtype=hist_type,
                                color='gold', alpha=alpha)
    ee = df[df.M==7][tw].hist(bins=hist_bins, histtype=hist_type,
                              color='orange', alpha=alpha)
    ff = df[df.M==7.5][tw].hist(bins=hist_bins, histtype=hist_type,
                                color='red', alpha=alpha)

    return aa, bb, cc, dd, ee, ff
```

```
#plot_probs(pv)

#xlabel('observation time, yrs', fontsize=18)
#ylabel('probability', fontsize=18)
#title('Figure 1: Likelihood of observing an earthquake \n'
#      + 'larger than M on the Kongur Shan fault', fontsize=21)
#show()
```

```
tw=35
nbins=1001
x_sec_lims = [0,0.4,0,400]

figure()

subplot2grid((3,2), (0,0), colspan=2)
axvline(tw, lw=0.5, color='grey')
plot_probs(pv, lw=0.2, alpha=0.2)

subplot2grid((3,2), (1,0), colspan=2)
axvline(tw, lw=0.5, color='grey')
plot_probs(pv_c, lw=0.2, alpha=0.2)

subplot2grid((3,2), (2,0))
pmf_x_secs(pv, tw, n_bins=nbins)
axis(x_sec_lims)

subplot2grid((3,2), (2,1))
pmf_x_secs(pv_c, tw, n_bins=nbins)
axis([0,0.1,0,400])

show()

```

The results for faults with a GR frequency-magnitude distribution
indicate that it is unlikely that any individual fault would have an earthquake
greater than $M \, 5$ in any modeled observation time (up to 100 years).
As an example, the results for the Panamint Valley fault are shown in
Figure~\ref{fig:pv}a;
this fault has the highest $P(M,t)$ of any of the well-studied LANFs.  The
probability of observing a $\ge M \, 6.0$ event on the Panamint Valley fault
is about 0.5 for $t$ = 100 years, and about 0.15 for $t$ = 35 years, which is
the length of the Global CMT catalog. As expected given the GR distribution,
the $P(M,t)$ is much higher for smaller, more frequent events than for larger
events.

The results for faults with a characteristic frequency-magnitude distribution
yield much lower $P(M,t)$ for small to moderate events, but for large events,
$P(M,t)$ is higher (Figure~\ref{fig:pv}b,d); this is because the earthquake
sequences are dominated by large, infrequent events, so the inter-event times
for moderate events are several times greater. For the Panamint Valley fault,
$P(M,t)$ for $ \ge M \, 5$ is about 0.07 (versus 0.25 for the GR distribution),
but $P(M\ge 7, t=35)$ is around 0.025 (versus essentially zero for the GR
distribution).  As the characteristic distribution likely better represents
earthquakes on an individual large fault, these results suggest that is
very unlikely that we would expect to capture any significant seismicity
on an single LANF in the moment tensor catalogs. A similar conclusion was
found by \citet{wernicke1995seis} based on a simple calculation,
assuming perfectly repeating large earthquakes on an idealized fault.

### Earthquake Likelihood on All LANFs

To calculate the probability of observing an earthquake over the time
window on any of the LANFs studied, we first assume that seismicity on
each fault is independent and uncorrelated with seismicity on all
other faults. This assumption is likely true for most faults, but may
not be true for some proximal faults (such as the North and South
Lunggar detachments, or the Papangeo and Tokorondo detachments),
though it is unclear how to determine how these faults may interact
such that an appropriate conditional or joint probability may be calculated.
We determine the probability for each time window and minimum magnitude
with the equation

\begin{equation}
P_{AT \, or \, LP\, \ldots \, or \, DV} = 1 - (Q_{AT} \cdot Q_{LP} \cdot \ldots \, \cdot Q_{DV})
%\label{ProbUnion}
\end{equation}

where $P_{AT}$ is the probability of observing an earthquake on a
single LANF (e.g., the Alto-Tiberina fault), and $Q_{AT} = 1 -
P_{AT}$. Equation (\ref{ProbUnion}) is the union of probabilities for
non mutually exclusive random events.

### Calculating these probabilities

####now load the rest:

Gutenberg-Richter models

```
ks = pd.read_csv('../results/kongur_shan_gr.csv', index_col=0)
gm = pd.read_csv('../results/gurla_mandhata_gr.csv', index_col=0)
slr = pd.read_csv('../results/s_lunggar_gr.csv', index_col=0)
nlr = pd.read_csv('../results/n_lunggar_gr.csv', index_col=0)
pqxn = pd.read_csv('../results/pqx_n_gr.csv', index_col=0)
pqxq = pd.read_csv('../results/pqx_qingdu_gr.csv', index_col=0)
lp = pd.read_csv('../results/leo_pargil_gr.csv', index_col=0)
nqtl = pd.read_csv('../results/nqtl_gr.csv', index_col=0)
at = pd.read_csv('../results/alto_tiberina_gr.csv', index_col=0)
dv = pd.read_csv('../results/death_valley_gr.csv', index_col=0)
cd = pd.read_csv('../results/canada_david_gr.csv', index_col=0)
sd = pd.read_csv('../results/sevier_desert_gr.csv', index_col=0)
dxv = pd.read_csv('../results/dixie_valley_gr.csv', index_col=0)
dd = pd.read_csv('../results/dayman_dome_gr.csv', index_col=0)
pp = pd.read_csv('../results/papangeo_gr.csv', index_col=0)
tk = pd.read_csv('../results/tokorondo_gr.csv', index_col=0)
cb = pd.read_csv('../results/cordillera_blanca_gr.csv', index_col=0)
kz = pd.read_csv('../results/kuzey_gr.csv', index_col=0)
gn = pd.read_csv('../results/guney_gr.csv', index_col=0)
```

characteristic models

```
ks_c = pd.read_csv('../results/kongur_shan_char.csv', index_col=0)
gm_c = pd.read_csv('../results/gurla_mandhata_char.csv', index_col=0)
slr_c = pd.read_csv('../results/s_lunggar_char.csv', index_col=0)
nlr_c = pd.read_csv('../results/n_lunggar_char.csv', index_col=0)
pqxn_c = pd.read_csv('../results/pqx_n_char.csv', index_col=0)
pqxq_c = pd.read_csv('../results/pqx_qingdu_char.csv', index_col=0)
lp_c = pd.read_csv('../results/leo_pargil_char.csv', index_col=0)
nqtl_c = pd.read_csv('../results/nqtl_char.csv', index_col=0)
at_c = pd.read_csv('../results/alto_tiberina_char.csv', index_col=0)
dv_c = pd.read_csv('../results/death_valley_char.csv', index_col=0)
cd_c = pd.read_csv('../results/canada_david_char.csv', index_col=0)
sd_c = pd.read_csv('../results/sevier_desert_char.csv', index_col=0)
dxv_c = pd.read_csv('../results/dixie_valley_char.csv', index_col=0)
dd_c = pd.read_csv('../results/dayman_dome_char.csv', index_col=0)
pp_c = pd.read_csv('../results/papangeo_char.csv', index_col=0)
tk_c = pd.read_csv('../results/tokorondo_char.csv', index_col=0)
cb_c = pd.read_csv('../results/cordillera_blanca_char.csv', index_col=0)
kz_c = pd.read_csv('../results/kuzey_char.csv', index_col=0)
gn_c = pd.read_csv('../results/guney_char.csv', index_col=0)
```

make list of faults

```
f_list = ['ks', 'lp', 'gm', 'slr', 'nlr', 'pqxn', 'pqxq', 'nqtl', 'at',
          'dv', 'pv', 'cd', 'sd', 'dxv', 'dd', 'pp', 'tk', 'cb', 'kz', 'gn']

fault_list = [ks, lp, gm, slr, nlr, pqxn, pqxq, nqtl, at,
              dv, pv, cd, sd,  dxv, dd, pp, tk, cb, kz, gn]
```

```
fault_c_list = [ks_c, lp_c, gm_c, slr_c, nlr_c, pqxn_c, pqxq_c, nqtl_c, at_c,
              dv_c, pv_c, cd_c, sd_c, dxv_c, dd_c, pp_c, tk_c, cb_c, kz_c, gn_c]

fc_list = ['ks_c', 'lp_c', 'gm_c', 'slr_c', 'nlr_c', 'pqxn_c',
                'pqxq_c', 'nqtl_c', 'at_c', 'dv_c', 'pv_c', 'cd_c',
                'sd_c', 'dxv_c', 'dd_c', 'pp_c', 'tk_c', 'cb_c', 'kz_c', 'gn_c']
```

#### Calculate $q$, probability of *not* observing an earthquake, for each fault

```
q = {}

for i, f_ in enumerate(f_list):
    q[f_] = fault_list[i].copy()
    q[f_][tw_cols] = 1 - q[f_][tw_cols]
```

```
qc = {}

for i, f_ in enumerate(fc_list):
    qc[f_] = fault_c_list[i].copy()
    qc[f_][tw_cols] = 1 - qc[f_][tw_cols]
```

#### Now estimate joint probabilities

Make list of columns to retain in final dataframe

```
prob_cols = list( chain( *['M', list(tw_cols)] ) )
```

calculate $p_{joint}$ as $1-(q \cdot q \cdot q...)$ and make final dataframe

```
all_prob_gr = 1 - np.product([qq[tw_cols] for qq in  q.values()])

all_prob_gr['M'] = ks['M']

all_prob_gr = all_prob_gr.reindex_axis(prob_cols, axis=1)
```

```
all_c_prob = 1- np.product([qq[tw_cols] for qq in qc.values()])

all_c_prob['M'] = ks['M']

all_c_prob = all_c_prob.reindex_axis(prob_cols, axis=1)
```

```
tw=35
nbins=501
x_sec_lims = [0,0.4,0,400]

figure()

subplot2grid((3,2), (0,0), colspan=2)
axvline(tw, lw=0.5, color='grey')
plot_probs(all_prob_gr, lw=0.1, alpha=0.1)

subplot2grid((3,2), (1,0), colspan=2)
axvline(tw, lw=0.5, color='grey')
plot_probs(all_c_prob, lw=0.1, alpha=0.1)

subplot2grid((3,2), (2,0))
pmf_x_secs(all_prob_gr, tw, n_bins=nbins)
#axis(x_sec_lims)

subplot2grid((3,2), (2,1))
pmf_x_secs(all_c_prob, tw, n_bins=nbins)
#axis([0,0.1,0,400])
xlim([0,0.8])
show()
```

```
tw=35
nbins=501
x_sec_lims = [0,0.4,0,400]

figure()

subplot2grid((2,2), (0,0))
axvline(tw, lw=0.5, color='grey')
plot_probs(all_prob_gr, lw=0.1, alpha=0.1)

subplot2grid((2,2), (0,1))
axvline(tw, lw=0.5, color='grey')
plot_probs(all_c_prob, lw=0.1, alpha=0.1)

subplot2grid((2,2), (1,0))
pmf_x_secs(all_prob_gr, tw, n_bins=nbins)
#axis(x_sec_lims)

subplot2grid((2,2), (1,1))
pmf_x_secs(all_c_prob, tw, n_bins=nbins)
#axis([0,0.1,0,400])
xlim([0,0.8])
show()
```

```

```


```python
#remove cell visibility
from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)
```

Toggle cell visibility here.

```python
%matplotlib inline
import control
import numpy
import sympy as sym
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    def dummychangecallback(self,change):
        pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(control.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        control.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

## Krmiljenje pozicije pnevmatskega sistema

Kroglica je nameščena v navpični cevi, skozi katero teče zrak s pretokom $ u $; z vzmetjo togosti $ K $ je povezana na dno cevi; krogla je podvržena gravitaciji in viskoznemu trenju s koeficientom $ B $. Sila $ F $, ki deluje na kroglo zaradi pretoka zraka, je sorazmerna pretok $ u $ (v m / s) prek konstante $ G $; pretok zraka je lahko samo pozitiven (v cev lahko zgolj vstopa). Glede na maso kroglice $ m = 2 $ g ter $ B = 1 $ g / s, $ K = 2 $ g / $ \ text {s} ^ 2 $ in $ G = 3 $ Ns / m, je cilj načrtovati regulator, z $ u $ kot vhodom in nadmorsko višino žoge $ z $ kot izhodom, v skladu z naslednjimi zahtevami:
- brez odstopka v stacionarnem stanju v odzivu na spremembo nadmorske višine v obliki koračne funkcije,
- maksimalen prenihaj 30%,
- čas ustalitve krajši od 8 s (dosežena vrednost izhoda naj se razlikuje od tiste v stacionarnem stanju za 5%).

Dinamiko sistem lahko popišemo z naslednjo enačbo:

$$
    m\ddot{z} = -Kz -B\dot{z} + Gu
$$

Ob upotševanju vektorja stanj $x=\begin{bmatrix} x_1 & x_2 \end{bmatrix}^T=\begin{bmatrix} z & \dot{z} \end{bmatrix}^T$ lahko zapišemo:

\begin{cases}
    \dot{x} = \begin{bmatrix} 0 & 1 \\ -\frac{K}{m} & -\frac{B}{m} \end{bmatrix}x + \begin{bmatrix} 0 \\ \frac{G}{m} \end{bmatrix}u \\
    y = \begin{bmatrix} 1 & 0 \end{bmatrix}x
\end{cases}

Pola sistema sta $-0.25\pm0.97i$.

### Načrtovanje regulatorja
#### Načrtovanje krmilnika

Da zagotovimo ničen odstopek v stacionarnem stanju, dodamo intergrator v sistem na način, da razširimo vektor stanj z $\dot{x_3} = x_1 - y_d$, kjer je $y_d$ referenca. Razširjen sistem je tako:

\begin{cases}
    \dot{x_a} = \begin{bmatrix} 0 & 1 & 0 \\ -\frac{K}{m} & -\frac{B}{m} & 0 \\ 1 & 0 & 0 \end{bmatrix}x_a + \begin{bmatrix} 0 & 0 \\ \frac{G}{m} & 0 \\ 0 & -1 \end{bmatrix}u \\
    y_a = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 1 \end{bmatrix}x_a \, .
\end{cases}

Ker so poli sistema počasni glede na zahteve, povečamo lastno frekvenco kompleksnih polov tako, da ju razporedimo v $-0.5\pm1.94$, hkrati pa zamenjamo integrator z polom v $-2$.

#### Načrtovanje spoznavalnika
Za oceno stanj razvijemo spoznavalnik podsistema $(x_1,\, x_2)$ in vzamemo zgolj oceno stanja $x_2$, ker lahko preostalo stanje neposredno merimo. Za lokacijo polov spoznavalnika izberemo točko $-5$.

### Kako upravljati s tem interaktivnim primerom?
- Oceni delovanje regulatorja v primeru relativne variacije mase kroglice (parameter $\delta m$) in, če je potrebno, spremeni razporeditev polov.
- Testiraj delovanje regulatorja v primeru začetne napake ocenjenega stanja.
- Testiraj delovanje regulatorja za različne vhodne funkcije.

```python
# Preparatory cell
b = 1E-03
k = 2E-03
G = 3
m = 2E-03
Aa = numpy.matrix([[0, 1, 0],
                   [-k/m, -b/m, 0],
                   [1, 0, 0]])
Ba = numpy.matrix([[0, 0],
                   [G/m, 0],
                   [0,-1]])
Ca = numpy.matrix([[1, 0, 0],
                   [0, 0, 1]])
A = Aa[0:2,0:2]
B = Ba[0:2,0]
C = Ca[0,0:2]

X0 = numpy.matrix('0.0')
K = numpy.matrix([8/15,-4.4,-4])
L = numpy.matrix([[23],[66]])

X0w = matrixWidget(1,1)
X0w.setM(X0)
Kw = matrixWidget(1,3)
Kw.setM(K)
Lw = matrixWidget(2,1)
Lw.setM(L)

eig1c = matrixWidget(1,1)
eig2c = matrixWidget(2,1)
eig3c = matrixWidget(1,1)
eig1c.setM(numpy.matrix([-2.0]))
eig2c.setM(numpy.matrix([[-0.5],[-1.94]]))
eig3c.setM(numpy.matrix([-2.0]))

eig1o = matrixWidget(1,1)
eig2o = matrixWidget(2,1)
eig1o.setM(numpy.matrix([-5.]))
eig2o.setM(numpy.matrix([[-5.],[0.]]))
```

```python
# Misc

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)

# Define type of method
selm = widgets.Dropdown(
    options= ['Nastavi K in L', 'Nastavi lastne vrednosti'],
    value= 'Nastavi lastne vrednosti',
    description='',
    disabled=False
)

# Define the number of complex eigenvalues
sele = widgets.Dropdown(
    options= ['brez kompleksnih lastnih vrednosti', 'dve kompleksni lastni vrednosti'],
    value= 'dve kompleksni lastni vrednosti',
    description='Kompleksne lastne vrednosti:',
    disabled=False
)

#define type of ipout
selu = widgets.Dropdown(
    options=['impulzna funkcija', 'koračna funkcija', 'sinusoidna funkcija', 'kvadratni val'],
    value='koračna funkcija',
    description='Motnja',
    style = {'description_width': 'initial'},
    disabled=False
)
# Define the values of the input
u = widgets.FloatSlider(
    value=0.1,
    min=0,
    max=0.8,
    step=0.1,
    description='Referenca [m]:',
    style = {'description_width': 'initial'},
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)
mw = widgets.FloatSlider(
    value=0,
    min=-30,
    max=30,
    step=1,
    description='delta m [%]:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)
period = widgets.FloatSlider(
    value=2,
    min=0.1,
    max=3,
    step=0.05,
    description='Perioda [s]: ',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
)
simTime = widgets.FloatText(
    value=8,
    description='',
    disabled=False
)
```

```python
# Support functions

def eigen_choice(sele):
    if sele == 'brez kompleksnih lastnih vrednosti':
        eig1c.children[0].children[0].disabled = False
        eig2c.children[1].children[0].disabled = True
        eig1o.children[0].children[0].disabled = False
        eig2o.children[1].children[0].disabled = True
        eig = 0
    if sele == 'dve kompleksni lastni vrednosti':
        eig1c.children[0].children[0].disabled = True
        eig2c.children[1].children[0].disabled = False
        eig1o.children[0].children[0].disabled = True
        eig2o.children[1].children[0].disabled = False
        eig = 2
    return eig

def method_choice(selm):
    if selm == 'Nastavi K in L':
        method = 1
        sele.disabled = True
    if selm == 'Nastavi lastne vrednosti':
        method = 2
        sele.disabled = False
    return method

def simulation(m,Kw,L,uw,selu,period,simTime,X0w):
    Aa = numpy.matrix([[0, 1, 0],
                   [-k/(0.002*(1+m/100.0)), -b/(0.002*(1+m/100.0)), 0],
                   [1, 0, 0]])
    Ba = numpy.matrix([[0, 0],
                       [G/(0.002*(1+m/100.0)), 0],
                       [0,-1]])
    A = Aa[0:2,0:2]
    B = Ba[0:2,0]

    e1 = numpy.linalg.eig(Aa-Ba[:,0]*Kw)[0]
    e1n = numpy.array([numpy.linalg.norm(e1[i]) for i in range(3)])
    e2 = numpy.linalg.eig(A-L*C)[0]
    e2n = numpy.array([numpy.linalg.norm(e2[i]) for i in range(2)])
    # print(e1n,e2n)

    n1 = 0
    if all(e1n > 0):
        if all(numpy.real(e1)<0):
            n1 = int(max(e1n)/2/numpy.pi*1000)
    if n1==0:
        n1 = 10000

    n2 = 0
    if all(e2n > 0):
        if all(numpy.real(e2)<0):
            n2 = int(max(e2n)/2/numpy.pi*1000)
    if n2==0:
        n2 = 10000
    if n1 >= n2:
        n = n1
    else:
        n = n2

    T = numpy.linspace(0,simTime,n)
    ts = T[1]-T[0]
    if selu == 'impulzna funkcija': #selu
        R = [0 for t in range(0,len(T))]
        R[0] = uw
    if selu == 'koračna funkcija':
        R = [uw for t in range(0,len(T))]
    if selu == 'sinusoidna funkcija':
        R = uw*numpy.sin(2*numpy.pi/period*T)
        #print(R)
    if selu == 'kvadratni val':
        R = uw*numpy.sign(numpy.sin(2*numpy.pi/period*T))
        #print(R)

    Aad = (numpy.eye(3)+ts*Aa)
    Bad = ts*Ba
    Ad = (numpy.eye(2)+ts*(A-L*C))
    Bd = ts*B
    Ld = ts*L
    xa = numpy.matrix([[0],[0],[0]])
    xe = numpy.matrix([[0],[X0w]])
    x = numpy.vstack((xa,xe))
    X = [x]
    U = []

    for i in range(len(T)):
        u = (-Kw*numpy.matrix([[xa[0,0]],[xe[1,0]],[xa[2,0]]]))[0,0]
        if u < 0:
            u = 0

        xe = Ad*xe+Bd*u+Ld*xa[0,0]
        xa = Aad*xa+Bad*numpy.matrix([[u],[R[i]]])

        X.append(numpy.vstack((xa,xe)))
        U.append(u)

    xout = [
        [X[i][0,0] for i in range(len(T))],
        [X[i][1,0] for i in range(len(T))],
        [X[i][2,0] for i in range(len(T))],
        [X[i][3,0] for i in range(len(T))],
        [X[i][4,0] for i in range(len(T))]
    ]
    return T, U, R, xout, e1, e2
```

```python
def main_callback2(mw, X0w, K, L, eig1c, eig2c, eig3c, eig1o, eig2o, u, period, selm, sele, selu, simTime, DW):
    eige = eigen_choice(sele)
    method = method_choice(selm)

    if method == 1:
        solc = numpy.linalg.eig(Aa-Ba[:,0]*K)[0]
        solo = numpy.linalg.eig(A-L*C)[0]
    if method == 2:
        if eige == 0:
            K = control.acker(Aa, Ba[:,0], [eig1c[0,0], eig2c[0,0], eig3c[0,0]])
            Kw.setM(K)
            L = control.acker(A.T, C.T, [eig1o[0,0], eig2o[0,0]]).T
            Lw.setM(L)
        if eige == 2:
            K = control.acker(Aa, Ba[:,0], [eig3c[0,0],
                                     numpy.complex(eig2c[0,0],eig2c[1,0]),
                                     numpy.complex(eig2c[0,0],-eig2c[1,0])])
            Kw.setM(K)
            L = control.acker(A.T, C.T, [numpy.complex(eig2o[0,0],eig2o[1,0]),
                                         numpy.complex(eig2o[0,0],-eig2o[1,0])]).T
            Lw.setM(L)

    if simTime != 0:
        pass
    else:
        simTime = 7

    T, U, R, xout, sol1, sol2 = simulation(mw,K,L,u,selu,period,simTime,X0w[0,0])

    if selu == 'koračna funkcija':
        found = False
        ST5 = 0
        for i in range(len(T)):
            if R[i] != 0:
                if abs((xout[0][i]-R[i])/R[i]) <= 0.05 and not found:
                    ST5 = T[i]
                    found = True
                if abs((xout[0][i]-R[i])/R[i]) > 0.05:
                    found = False
        OV = 0
        for i in range(len(T)):
            if R[i] != 0:
                if R[0]>0:
                    if (xout[0][i]-R[i]) > 0:
                        if abs((xout[0][i]-R[i])/R[i])*100 > OV:
                            OV = abs((xout[0][i]-R[i])/R[i])*100
                else:
                    if (xout[0][i]-R[i]) < 0:
                        if abs((xout[0][i]-R[i])/R[i])*100 > OV:
                            OV = abs((xout[0][i]-R[i])/R[i])*100
    else:
        ST5 = 'Ni definiran'
        OV = 'Ni definiran'
    print('Lastne vrednosti zaprtozančnega sistema:',sol1)
    print('Lastne vrednosti spoznavalnika:',sol2)
    print('Informacije o odzivu sistema: \n\tČas ustalitve (5%) [s]=',ST5,'\n\tPrenihaj [%]=',OV)

    fig = plt.figure(num='Simulacija 1', figsize=(14,12))

    fig.add_subplot(221)
    plt.title('Odziv sistema')
    plt.ylabel('Izhod [m]')
    plt.plot(T,xout[0])
    plt.plot(T,R,'g--')
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$y$','Referenca'])
    plt.grid()

    fig.add_subplot(222)
    plt.title('Vhod')
    plt.ylabel('$u$ [N]')
    plt.plot(T,U)
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.grid()

    fig.add_subplot(223)
    plt.title('Odziv stanj')
    plt.ylabel('Stanje')
    plt.plot(T,xout[0],
             T,xout[1],
             T,xout[2])
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$x_{1}$','$x_{2}$','$x_{3}$'])
    plt.grid()

    fig.add_subplot(224)
    plt.title('Napaka ocene stanj')
    plt.ylabel('Napaka ocene stanj')
    plt.plot(T,numpy.array(xout[1])-numpy.array(xout[4]))
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$e_{2}$'])
    plt.grid()
    #plt.tight_layout()

alltogether2 = widgets.VBox([widgets.HBox([selm,
                                          sele,
                                          selu]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.Label('K:',border=3), Kw,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('Lastne vrednosti:',border=3),
                                          eig1c,
                                          eig2c,
                                          eig3c,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('X0 est.:',border=3), X0w]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.Label('L:',border=3), Lw,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('Lastne vrednosti:',border=3),
                                          eig1o,
                                          eig2o,
                                          widgets.Label(' ',border=3),
                                          widgets.VBox([widgets.Label('Simulacijski čas [s]:',border=3)]),
                                          widgets.VBox([simTime])]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([u,
                                          mw,
                                          period,
                                          START])])
out2 = widgets.interactive_output(main_callback2, {'mw':mw, 'X0w':X0w, 'K':Kw, 'L':Lw,
                                                 'eig1c':eig1c, 'eig2c':eig2c, 'eig3c':eig3c, 'eig1o':eig1o, 'eig2o':eig2o,
                                                 'u':u, 'period':period, 'selm':selm, 'sele':sele, 'selu':selu, 'simTime':simTime, 'DW':DW})
out2.layout.height = '860px'
display(out2, alltogether2)
```

    Output(layout=Layout(height='860px'))

    VBox(children=(HBox(children=(Dropdown(index=1, options=('Nastavi K in L', 'Nastavi lastne vrednosti'), value=…