# Body segment parameters

> Marcos Duarte
> [Laboratory of Biomechanics and Motor Control](http://pesquisa.ufabc.edu.br/bmclab/)
> Federal University of ABC, Brazil

"Le proporzioni del corpo umano secondo Vitruvio", also known as the Vitruvian Man, drawing by Leonardo da Vinci circa 1490 based on the work of Marcus Vitruvius Pollio (1st century BC), depicting a man in supposedly ideal human proportions (image from Wikipedia).

In fact, Leonardo's drawing does not follow the proportions according Vitruvius, but rather the proportions he found after his own anthropometrical studies of the human body. Leonardo was unable to fit a human body inside a circle and a square with the same center, one of the Vitruvius' claims.

This is a remarkable historical evidence of not complying with established common knowledge and relying on experimental data for acquiring knowledge about nature, a tour de force for the scientific method.

Contents

1  Estimation of body segment parameters

1.1  Dempster's model adapted by Winter

1.2  Zatsiorsky's model adjusted by de Leva

1.3  Differences between the anthropometric models from Dempster and Zatsiorsky

2  Center of mass

3  Moment of inertia

3.1  Radius of gyration

3.2  Parallel axis theorem

4  Center of buoyancy

5  Further reading

6  Video lectures on the internet

7  Problems

8  References

## Estimation of body segment parameters

Body segment parameters (BSP) of the human body, such as length, area, volume, mass, density, center of mass, moment of inertia, and center of volume, are fundamental for the application of mechanics to the understanding of human movement. Anthropometry is the field concerned with the study of such measurements of the human body. Frequently, one cannot measure most of these parameters of each segment of an individual and these quantities are estimated by indirect methods. The main indirect methods are based in data of cadavers (e.g. Dempster's model), body image scanning of living subjects (e.g., Zatsiorsky-Seluyanov's model), and geometric measurements (e.g., Hanavan's model).

For reviews available online of the different methods employed in the estimation of BSP, see [Drills et al. (1964)](http://www.oandplibrary.org/al/1964_01_044.asp) and [Bjørnstrup (1995)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.5223).

Let's look on how to estimate some of the BSP using the anthropometric model of Dempster (1955) with some parameters adapted by Winter (2009) and the model of Zatsiorsky and Seluyanov (Zatsiorsky, 2002), from now on, Zatsiorsky, with parameters adjusted by de Leva (1996). There is at least one Python library for the calculation of human body segment parameters, see Dembia et al. (2014), it implements the Yeadon human inertia geometric model, but we will not use it here.

For a table with BSP values, also referred as anthropometric table, typically:

+ The mass of each segment is given as fraction of the total body mass.
+ The center of mass (CM) position in the sagittal plane of each segment is given as fraction of the segment length with respect to the proximal or distal joint position.
+ The radius of gyration (Rg) around the transverse axis (rotation at the sagittal plane) and around other axes of each segment is given as fraction of the segment length with respect to (w.r.t.) the center of mass or w.r.t. the proximal or w.r.t. the distal joint position.

For a formal description of these parameters, see the notebook [Center of Mass and Moment of Inertia](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/CenterOfMassAndMomentOfInertia.ipynb).

```python
# Import the necessary libraries
import numpy as np
import pandas as pd
from IPython.display import display, Math, Latex
%matplotlib inline
import matplotlib.pyplot as plt
pd.set_option('max_colwidth', 100)
```

### Dempster's model adapted by Winter

```python
BSP_Dmarks = pd.read_csv('./../data/BSPlandmarks_Dempster.txt', sep='\t')
display(Latex('BSP segments from Dempster\'s model adapted by Winter (2009):'))
display(BSP_Dmarks)
```

BSP segments from Dempster's model adapted by Winter (2009):

      Segment
      Definition

      0
      Hand
      Wrist axis/knuckle II middle finger

      1
      Forearm
      Elbow axis/ulnar styloid

      2
      Upper arm
      Glenohumeral axis/elbow axis

      3
      Forearm and hand
      Elbow axis/ulnar styloid

      4
      Total arm
      Glenohumeral joint/ulnar styloid

      5
      Foot
      Lateral malleolus/head metatarsalII

      6
      Leg
      Femoral condyles/medial malleolus

      7
      Thigh
      Greater trochanter/femoral condyles

      8
      Foot and leg
      Femoral condyles/medial malleolus

      9
      Total leg
      Greater trochanter/medial malleolus

      10
      Head and neck
      C7-T1 and 1st rib/ear canal

      11
      Shoulder mass
      Sternoclavicular joint/glenohumeral

      12
      Thorax
      C7-T1/T12-L1 and diaphragm

      13
      Abdomen
      T12-L1/L4-L5

      14
      Pelvis
      L4-L5/greater trochanter

      15
      Thorax and abdomen
      C7-T1/L4-L5

      16
      Abdomen and pelvis
      T12-L1/greater trochanter

      17
      Trunk
      Greater trochanter/glenohumeral joint

      18
      Trunk head neck
      Greater trochanter/glenohumeral joint

      19
      Head, arms, and trunk (HAT)
      Greater trochanter/glenohumeral joint

      20
      HAT
      Greater trochanter/mid rib

```python
bsp_D = pd.read_csv('./../data/BSP_DempsterWinter.txt', index_col=0, sep='\t')
display(Latex('BSP values from Dempster\'s model adapted by Winter (2009):'))
display(bsp_D)
```

BSP values from Dempster's model adapted by Winter (2009):

      Definition
      Mass
      CM prox
      CM dist
      Rg CM
      Rg prox
      Rg dist

      Segment

      Hand
      WJC-KNU2
      0.0060
      0.506
      0.494
      0.297
      0.587
      0.577

      Forearm
      EJC-STYL
      0.0160
      0.430
      0.570
      0.303
      0.526
      0.647

      Upper arm
      SJC-EJC
      0.0280
      0.436
      0.564
      0.322
      0.542
      0.645

      Forearm hand
      EJC-STYL
      0.0220
      0.682
      0.318
      0.468
      0.827
      0.565

      Total arm
      SJC-STYL
      0.0500
      0.530
      0.470
      0.368
      0.645
      0.596

      Foot
      LMAL-MT2
      0.0145
      0.500
      0.500
      0.475
      0.690
      0.690

      Leg
      KJC-MMAL
      0.0465
      0.433
      0.567
      0.302
      0.528
      0.643

      Thigh
      GTR-KJC
      0.1000
      0.433
      0.567
      0.323
      0.540
      0.653

      Head neck
      C7T1-RIB1EAR
      0.0810
      1.000
      0.000
      0.495
      0.116
      NaN

      Thorax
      C7T1-T12L1-DIAP
      0.2160
      0.820
      0.180
      NaN
      NaN
      NaN

      Abdomen
      T12L1-GTR
      0.1390
      0.440
      0.560
      NaN
      NaN
      NaN

      Pelvis
      L4L5-GTR
      0.1420
      0.105
      0.895
      NaN
      NaN
      NaN

      Trunk
      GTR-SJC
      0.4970
      0.500
      0.500
      NaN
      NaN
      NaN

      Trunk head neck
      GTR-SJC
      0.5780
      0.660
      0.340
      0.503
      0.830
      0.607

      HAT
      GTR-SJC
      0.6780
      0.626
      0.374
      0.496
      0.798
      0.621

      Foot leg
      KJC-MMAL
      0.0610
      0.606
      0.394
      0.416
      0.735
      0.572

      Total leg
      GTR-MMAL
      0.1610
      0.447
      0.553
      0.326
      0.560
      0.650

      Thorax abdomen
      C7T1-L4L5
      0.3550
      0.630
      0.370
      NaN
      NaN
      NaN

      Abdomen pelvis
      T12L1-GTR
      0.2810
      0.270
      0.730
      NaN
      NaN
      NaN

### Zatsiorsky's model adjusted by de Leva

The segments defined in the Zatsiorsky's model (Zatsiorsky, 2002) adjusted by de Leva (1996) are illustrated in the next figure.

Figure. Segment definition employed in the anthropometric model of Zatsiorsky and Seluyanov (Zatsiorsky, 2002) adjusted by de Leva (1996).
Image from a Motion Analysis Corporation manual.

```python
BSP_Zmarks = pd.read_csv('./../data/BSPlandmarks_ZdeLeva.txt', sep='\t')
display(Latex('BSP landmarks from Zatsiorsky\'s model adjusted by de Leva (1996):'))
display(BSP_Zmarks)
```

BSP landmarks from Zatsiorsky's model adjusted by de Leva (1996):

      Landmark Name
      Abbreviation
      Description

      0
      Acromion
      ACRO
      Most lateral point on the lateral edge of the acromial process of the scapula

      1
      Acropodion
      TTIP
      Tip of longest toe

      2
      Bispinous breadth
      BB
      Distance between two ASIS

      3
      Cervicale
      CERV
      Superior tip of the spine of the 7th certical vertebra

      4
      Dactylion (3rd)
      DAC3
      Tip of 3 rd digit

      5
      Gonion
      GONI
      Most lateral point on the posterior angle of mandible

      6
      Iliospinale
      ASIS
      Inferior point of one of the anterior superior iliac spines

      7
      Malleoli
      MMAL, LMAL
      Medial and lateral bony projections of the malleolus

      8
      Metacarpale (3rd)
      MET3
      Distal palpable point on the metacarpal of the 3rd digit on the dorsal hand

      9
      Mid-gonion
      MIDG
      Point midway between 2 gonion

      10
      Mid-hip
      MIDH
      Point midway between 2 hip joint centers

      11
      Mid-shoulder
      MIDS
      Point midway between 2 shoulder joint centers

      12
      Omphalion
      OMPH
      Center of navel

      13
      Pternion
      HEEL
      Posterior point of the heel

      14
      Radiale
      RADI
      Lateral tip on the proximal head of the radius

      15
      Sphyrion (tibia)
      TSPH
      Distal tip of the tibia – distal to medial malleolus

      16
      Sphyrion fibulare
      FSPH
      Distal tip of the fibula – distal to lateral malleolus

      17
      Stylion
      RSTY
      Distal dip of the styloid process of the radius

      18
      Suprasternale
      SUPR
      Most caudal point on the jugular notch on the sternum

      19
      Tibiale (medial)
      MTIB
      Most proximal point on the medial superior border of the head of the tibia

      20
      Tibiale laterale
      LTIB
      Most proximal point on the lateral superior border of the head of the fibula

      21
      Trochanterion
      TROC
      Superior border on the greater trochanter of the femur

      22
      Vertex
      VERT
      Uppermost part of the head

      23
      Xiphion
      XYPH
      Lowermost end of the sternum

```python
bsp_Zf = pd.read_csv('./../data/BSPfemale_ZdeLeva.txt', index_col=0, sep='\t')
display(Latex('BSP female values from Zatsiorsky\'s model adjusted by de Leva (1996):'))
display(bsp_Zf)
```

BSP female values from Zatsiorsky's model adjusted by de Leva (1996):

      Endpoints
      Mass
      CM Long
      Rg Sag
      Rg Trans
      Rg Long

      Segment

      Head
      VERT-MIDG
      0.0668
      0.5894
      0.330
      0.359
      0.318

      Trunk
      SUPR-MIDH
      0.4257
      0.4151
      0.357
      0.339
      0.171

      Upper trunk
      SUPR-XYPH
      0.1545
      0.2077
      0.746
      0.502
      0.718

      Middle trunk
      XYPH-OMPH
      0.1465
      0.4512
      0.433
      0.354
      0.415

      Lower trunk
      OMPH-MIDH
      0.1247
      0.4920
      0.433
      0.402
      0.444

      Upper arm
      SJC-EJC
      0.0255
      0.5754
      0.278
      0.260
      0.148

      Forearm
      EJC-WJC
      0.0138
      0.4559
      0.261
      0.257
      0.094

      Hand
      WJC-MET3
      0.0056
      0.7474
      0.531
      0.454
      0.335

      Thigh
      HJC-KJC
      0.1478
      0.3612
      0.369
      0.364
      0.162

      Shank
      KJC-AJC
      0.0481
      0.4352
      0.267
      0.263
      0.092

      Foot
      HEEL-TTIP
      0.0129
      0.4014
      0.299
      0.279
      0.139

      Head 2
      VERT-CERV
      0.0668
      0.4841
      0.271
      0.295
      0.261

      Trunk 2
      CERV-MIDH
      0.4257
      0.4964
      0.307
      0.292
      0.147

      Trunk 3
      MIDS-MIDH
      0.4257
      0.3782
      0.379
      0.361
      0.182

      Upper trunk 2
      CERV-XYPH
      0.1545
      0.5050
      0.466
      0.314
      0.449

      Forearm 2
      EJC-STYL
      0.0138
      0.4592
      0.263
      0.259
      0.095

      Hand 2
      WJC-DAC3
      0.0056
      0.3427
      0.244
      0.208
      0.154

      Hand 3
      STYL-DAC3
      0.0056
      0.3502
      0.241
      0.206
      0.152

      Hand 4
      STYL-MET3
      0.0056
      0.7534
      0.519
      0.443
      0.327

      Shank 2
      KJC-LMAL
      0.0481
      0.4416
      0.271
      0.267
      0.093

      Shank 3
      KJC-SPHY
      0.0481
      0.4481
      0.275
      0.271
      0.094

```python
bsp_Zm = pd.read_csv('./../data/BSPmale_ZdeLeva.txt', index_col=0, sep='\t')
display(Latex('BSP male values from Zatsiorsky\'s model adjusted by de Leva (1996):'))
display(bsp_Zm)
```

BSP male values from Zatsiorsky's model adjusted by de Leva (1996):

      Endpoints
      Mass
      CM Long
      Rg Sag
      Rg Trans
      Rg Long

      Segment

      Head
      VERT-MIDG
      0.0694
      0.5976
      0.362
      0.376
      0.312

      Trunk
      SUPR-MIDH
      0.4346
      0.4486
      0.372
      0.347
      0.191

      Upper trunk
      SUPR-XYPH
      0.1596
      0.2999
      0.716
      0.454
      0.659

      Middle trunk
      XYPH-OMPH
      0.1633
      0.4502
      0.482
      0.383
      0.468

      Lower trunk
      OMPH-MIDH
      0.1117
      0.6115
      0.615
      0.551
      0.587

      Upper arm
      SJC-EJC
      0.0271
      0.5772
      0.285
      0.269
      0.158

      Forearm
      EJC-WJC
      0.0162
      0.4574
      0.276
      0.265
      0.121

      Hand
      WJC-MET3
      0.0061
      0.7900
      0.628
      0.513
      0.401

      Thigh
      HJC-KJC
      0.1416
      0.4095
      0.329
      0.329
      0.149

      Shank
      KJC-AJC
      0.0433
      0.4395
      0.251
      0.246
      0.102

      Foot
      HEEL-TTIP
      0.0137
      0.4415
      0.257
      0.245
      0.124

      Head 2
      VERT-CERV
      0.0694
      0.5002
      0.303
      0.315
      0.261

      Trunk 2
      CERV-MIDH
      0.4346
      0.5138
      0.328
      0.306
      0.169

      Trunk 3
      MIDS-MIDH
      0.4346
      0.4310
      0.384
      0.358
      0.197

      Upper trunk 2
      CERV-XYPH
      0.1596
      0.5066
      0.505
      0.320
      0.465

      Forearm 2
      EJC-STYL
      0.0162
      0.4608
      0.278
      0.267
      0.122

      Hand 2
      WJC-DAC3
      0.0061
      0.3624
      0.288
      0.235
      0.184

      Hand 3
      STYL-DAC3
      0.0061
      0.3691
      0.285
      0.233
      0.182

      Hand 4
      STYL-MET3
      0.0061
      0.7948
      0.614
      0.502
      0.392

      Shank 2
      KJC-LMAL
      0.0433
      0.4459
      0.255
      0.249
      0.103

      Shank 3
      KJC-SPHY
      0.0433
      0.4524
      0.258
      0.253
      0.105

### Differences between the anthropometric models from Dempster and Zatsiorsky

The anthropometric models from Dempster and Zatsiorsky are different in many aspects; regarding the subjetcs investigated in the studies, Dempster's model is based on the data of 8 cadavers of older male individuals (but two of the individuals were of unknown age) analyzed in the United States. Zatsiorsky's model is based on image scanning of 100 young men and 15 young women, at the time all students of a military school in the former Soviet Union.

The difference between models for some segments is large (see table below): the mass fraction of the thigh segment for Zatsiorsky's model is more than 40% larger than for the Dempster's model, inversely, the trunk segment has about 15% lower mass fraction for Zatisorsky's model. Also, note that some of the  segments don't have the same definition in the two models.

```python
m_D = bsp_D.loc[['Foot', 'Leg', 'Thigh', 'Pelvis', 'Abdomen', 'Thorax', 'Trunk',
                   'Upper arm', 'Forearm', 'Hand', 'Head neck'], 'Mass']
m_Zf = bsp_Zf.loc[['Foot', 'Shank', 'Thigh', 'Lower trunk', 'Middle trunk', 'Upper trunk',
                   'Trunk', 'Upper arm', 'Forearm', 'Hand', 'Head'], 'Mass']
m_Zm = bsp_Zm.loc[['Foot', 'Shank', 'Thigh', 'Lower trunk', 'Middle trunk', 'Upper trunk',
                   'Trunk', 'Upper arm', 'Forearm', 'Hand', 'Head'], 'Mass']
m_D.index = m_Zf.index  # because of different names for some segments

display(Latex("Mass fraction difference (in %) of Zatsiorsky's model w.r.t. Dempster's model"))
d = pd.DataFrame({'Females': np.around(100 * (m_Zf - m_D) / m_D), \
                  'Males': np.around(100 * (m_Zm - m_D) / m_D)})
display(d)
```

Mass fraction difference (in %) of Zatsiorsky's model w.r.t. Dempster's model

      Females
      Males

      Segment

      Foot
      -11.0
      -6.0

      Shank
      3.0
      -7.0

      Thigh
      48.0
      42.0

      Lower trunk
      -12.0
      -21.0

      Middle trunk
      5.0
      17.0

      Upper trunk
      -28.0
      -26.0

      Trunk
      -14.0
      -13.0

      Upper arm
      -9.0
      -3.0

      Forearm
      -14.0
      1.0

      Hand
      -7.0
      2.0

      Head
      -18.0
      -14.0

## Center of mass

See the notebook [Center of Mass and Moment of Inertia](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/CenterOfMassAndMomentOfInertia.ipynb) for a description of center of mass.

Using the data of the body segment parameters table, the center of mass of a single segment $i$ is (see figure below):

\begin{equation}
r_{i} = r_{i,p} + \text{bsp[i,cmp]} \cdot (r_{i,d}-r_{i,p})
\label{}
\end{equation}

Where $r_{i,p}$ and $\:r_{i,d}$ are the positions of the proximal and distal landmarks used to define the $i$ segment.
Note that $r$ is a vector and may have more than one dimension. The equation for the center of mass is valid in each direction and the calculations are performed independently in each direction. In addition, there is no need to include the mass of the segment in the equation above; the mass of the segment is used only when there is more than one segment.

For example, given the following coordinates ($x, y$) for the MT2, ankle, knee and hip joints:

```python
r = np.array([[101.1, 1.3], [84.9, 11.0], [86.4, 54.9], [72.1, 92.8]])/100
display(np.around(r, 3))
```

    array([[1.011, 0.013],
           [0.849, 0.11 ],
           [0.864, 0.549],
           [0.721, 0.928]])

The position of the center of mass of each segment and of the lower limb are:

```python
M = bsp_D.loc[['Foot', 'Leg', 'Thigh'], 'Mass'].sum()
rcm_foot  = r[1] + bsp_D.loc['Foot', 'CM prox']*(r[0]-r[1])
rcm_leg   = r[2] + bsp_D.loc['Leg',  'CM prox']*(r[1]-r[2])
rcm_thigh = r[3] + bsp_D.loc['Thigh','CM prox']*(r[2]-r[3])
rcm = (bsp_D.loc['Foot','Mass']*rcm_foot + bsp_D.loc['Leg','Mass']*rcm_leg + \
       bsp_D.loc['Thigh','Mass']*rcm_thigh)/M
print('Foot CM: ', np.around(rcm_foot, 3), 'm')
print('Leg CM: ', np.around(rcm_leg, 3), 'm')
print('Thigh CM: ', np.around(rcm_thigh, 3), 'm')
print('Lower limb CM: ', np.around(rcm, 3), 'm')
```

    Foot CM:  [0.93  0.062] m
    Leg CM:  [0.858 0.359] m
    Thigh CM:  [0.783 0.764] m
    Lower limb CM:  [0.818 0.584] m

And here is a geometric representation of part of these calculations:

```python
plt.rc('axes',  labelsize=14, linewidth=1.5)
plt.rc('xtick', labelsize=14)
plt.rc('ytick', labelsize=14)
plt.rc('lines', markersize=8)
hfig, hax = plt.subplots(1, 1, figsize=(10, 5))

# bones and joints
plt.plot(r[:,0], r[:,1], 'b-')
plt.plot(r[:,0], r[:,1], 'ko', label='joint')
# center of mass of each segment
plt.plot(rcm_foot[0], rcm_foot[1], 'go', label='segment center of mass')
plt.plot(rcm_leg[0], rcm_leg[1], 'go', rcm_thigh[0], rcm_thigh[1], 'go')
# total center of mass
plt.plot(rcm[0], rcm[1], 'ro', label='total center of mass')
hax.legend(frameon=False, loc='upper left', fontsize=12, numpoints=1)
plt.arrow(0, 0, r[3,0], r[3,1], color='b', head_width=0.02, overhang=.5, fc="k", ec="k",
          lw=2, length_includes_head=True)
plt.arrow(r[3,0], r[3,1], rcm_thigh[0] - r [3,0], rcm_thigh[1] - r[3,1], head_width=0.02,
      overhang=.5, fc="b", ec="b", lw=2, length_includes_head=True)
plt.arrow(0, 0, rcm_thigh[0], rcm_thigh[1], head_width=0.02, overhang=.5, fc="g", ec="g",
          lw=2, length_includes_head=True)
plt.text(0.30, .5, '$\mathbf{r}_{thigh,p}$', rotation=38, fontsize=16)
plt.text(0.77, .85, '$bsp_{thigh,cmp}*(\mathbf{r}_{i,d}-\mathbf{r}_{i,p})$',
         fontsize=16, color='b')
plt.text(0.15, .05,
         '$\mathbf{r}_{thigh,cm}=\mathbf{r}_{i,p}+bsp_{i,cmp}*' +
         '(\mathbf{r}_{i,d}-\mathbf{r}_{i,p})$',
         rotation=25, fontsize=16, color='g')
hax.set_xlim(0,1.1)
hax.set_ylim(0,1.05)
hax.set_xlabel('x [m]')
hax.set_ylabel('y [m]')
hax.set_title('Determination of center of mass', fontsize=16)
hax.grid()
```

## Moment of inertia

### Radius of gyration

See the notebook [Center of Mass and Moment of Inertia](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/CenterOfMassAndMomentOfInertia.ipynb) for a description of moment of inertia and radius of gyration.

The radius of gyration (as a fraction of the segment length) is the quantity that is given in the table of body segment parameters. Because of that, we don't need to sum each element of mass of the segment to calculate its moment of inertia; we just need to take the mass of the segment times the radius or gyration squared.

Using the body segment parameters, the moment of inertia of a single segment $i$ rotating around its own center of mass is (see figure below):

\begin{equation}
I_{i,cm} = M \cdot \text{bsp[i,mass]} \cdot \left(\text{bsp[i,rgcm]} \cdot ||r_{i,d}-r_{i,p}||\right)^2
\label{}
\end{equation}

Where $M$ is the total body mass of the subject and $||r_{i,d}-r_{i,p}||$ is the length of the segment $i$.

For example, the moment of inertia of each segment of the lower limb around each corresponding segment center of mass considering the coordinates (x, y) for the MT2, ankle, knee and hip joints given above are:

```python
norm = np.linalg.norm
M = 100  # body mass
Icm_foot = M*bsp_D.loc['Foot', 'Mass']*((bsp_D.loc['Foot', 'Rg CM']*norm(r[0]-r[1]))**2)
Icm_leg = M*bsp_D.loc['Leg',  'Mass']*((bsp_D.loc['Leg',  'Rg CM']*norm(r[1]-r[2]))**2)
Icm_thigh = M*bsp_D.loc['Thigh','Mass']*((bsp_D.loc['Thigh','Rg CM']*norm(r[2]-r[3]))**2)
print('Icm foot: ', np.around(Icm_foot, 3), 'kgm2')
print('Icm leg: ', np.around(Icm_leg, 3), 'kgm2')
print('Icm thigh: ', np.around(Icm_thigh, 3), 'kgm2')
```

    Icm foot:  0.012 kgm2
    Icm leg:  0.082 kgm2
    Icm thigh:  0.171 kgm2

### Parallel axis theorem

See the notebook [Center of Mass and Moment of Inertia](https://nbviewer.jupyter.org/github/BMClab/bmc/blob/master/notebooks/CenterOfMassAndMomentOfInertia.ipynb) for a description of parallel axis theorem.

For example, using the parallel axis theorem the moment of inertia of the lower limb around its center of mass is:

```python
Icmll = (Icm_foot  + M*bsp_D.loc['Foot', 'Mass']*norm(rcm-rcm_foot )**2 + \
         Icm_leg   + M*bsp_D.loc['Leg',  'Mass']*norm(rcm-rcm_leg  )**2 + \
         Icm_thigh + M*bsp_D.loc['Thigh','Mass']*norm(rcm-rcm_thigh)**2)

print('Icm lower limb: ', np.around(Icmll, 3), 'kgm2')
```

    Icm lower limb:  1.257 kgm2

To calculate the moment of inertia of the lower limb around the hip, we use again the parallel axis theorem:

```python
Ihipll = (Icm_foot  + M*bsp_D.loc['Foot', 'Mass']*norm(r[3]-rcm_foot )**2 + \
          Icm_leg   + M*bsp_D.loc['Leg',  'Mass']*norm(r[3]-rcm_leg  )**2 + \
          Icm_thigh + M*bsp_D.loc['Thigh','Mass']*norm(r[3]-rcm_thigh)**2)

print('Ihip lower limb: ', np.around(Ihipll, 3), 'kgm2')
```

    Ihip lower limb:  3.317 kgm2

Note that for the correct use of the parallel axis theorem we have to input the moment of inertia around the center of mass of each body. For example, we CAN NOT calculate the moment of inertia around the hip with the moment of inertia of the entire lower limb:

```python
# THIS IS WRONG:
I = (Icm_foot  + M*bsp_D.loc['Foot', 'Mass']*norm(r[3]-rcm)**2 + \
     Icm_leg   + M*bsp_D.loc['Leg',  'Mass']*norm(r[3]-rcm)**2 + \
     Icm_thigh + M*bsp_D.loc['Thigh','Mass']*norm(r[3]-rcm)**2)

print('Icm lower limb: ', np.around(I, 3), 'kgm2. THIS IS WRONG!')
```

    Icm lower limb:  2.324 kgm2. THIS IS WRONG!

## Center of buoyancy

[Center of buoyancy](https://en.wikipedia.org/wiki/Buoyancy) is the center of the volume of water which the submerged part of an object displaces. Center of buoyancy is to center of volume as center of gravity is to center of mass.

For the human body submerged in water, because different parts of the body have different densities, the center of buoyancy is at a different place than the center of gravity, see for example [Yanai and Wilson (2008)](https://onlinelibrary.wiley.com/doi/pdf/10.1002/jst.23).

Figure. Forces of gravity and buoyancy acting respectively on the center of gravity and center of buoyancy in a submerged human body (figure from Yanai and Wilson (2008)).

## Further reading

- [Drill et a. (1964)](http://www.oandplibrary.org/al/1964_01_044.asp) and [Bjørnstrup (1995)](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.5223) for a historical overview on the estimation of body segment parameters.

## Video lectures on the internet

- [Body segments parameters in Biomechanics](https://youtu.be/8hyPiha-lFU)
- [Estimating and Visualizing the Inertia of the Human Body with Python](https://youtu.be/H9AK65ZY-Vw)

## Problems

1. Take a picture at the frontal plane of somebody standing on one foot on tiptoes with the arms and the other leg abducted at the horizontal.
  1. Estimate the body center of mass of this person. Hint: for simplicity, consider the center of mass of each segment to be located at the middle of the segment and measure these positions using a image digitizer, e.g., [WebPlotDigitizer](https://automeris.io/WebPlotDigitizer/).
  2. If the person is almost standing still, through which part of the body a vertical line through the center of mass should necessarily pass? Have you obtained this result? Comment on possible differences between the expected and obtained results.

2. Consider the kinematic data from table A.1 of the Winter's book (Winter, 2009) used in problem 2 of the notebook [Angular kinematics in a plane (2D)](http://nbviewer.jupyter.org/github/demotu/BMC/blob/master/notebooks/KinematicsAngular2D.ipynb).
  1. Calculate the center of mass position for each segment and for the whole body (beware that no data are given for the head and arms segments) using the Dempster's and Zatsiorsky's models.
  2. Perform these calculations also for the moment of inertia (of each segment and of the whole body around the corresponding centers of mass).

3. Consider the following positions of markers placed on a leg (described in the laboratory coordinate system with coordinates $x, y, z$ in cm, the $x$ axis points forward and the $y$ axes points upward): lateral malleolus (**lm** = [2.92, 10.10, 18.85]), medial malleolus (**mm** = [2.71, 10.22, 26.52]), fibular head (**fh** = [5.05, 41.90, 15.41]), and medial condyle (**mc** = [8.29, 41.88, 26.52]). Define the ankle joint center as the centroid between the **lm** and **mm** markers and the knee joint center as the centroid between the **fh** and **mc** markers (same data as in problem 1 of the notebook [Rigid-body transformations (3D)](http://nbviewer.ipython.org/github/demotu/BMC/blob/master/notebooks/Transformation3D.ipynb)). Consider that the principal axes of the leg are aligned with the axes of the respective anatomical coordinate system.
  1. Determine the center of mass position of the leg at the anatomical and laboratory coordinate systems.
  2. Determine the inertia tensor of the leg for a rotation around its proximal joint and around its center of mass.

## References

- Bjørnstrup J (1995) [Estimation of Human Body Segment Parameters - Historical Background](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.21.5223). Technical Report.
- de Leva P (1996) [Adjustments to Zatsiorsky-Seluyanov's segment inertia parameters](http://ebm.ufabc.edu.br/wp-content/uploads/2013/12/Leva-1996.pdf). Journal of Biomechanics, 29, 9, 1223-1230.
- Dembia C, Moore JK, Hubbard M (2014) [An object oriented implementation of the Yeadon human inertia model](https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4329601/). F1000Research. 2014;3:223. doi:10.12688/f1000research.5292.2.
- Drills R, Contini R, Bluestein M (1964) [Body segment parameters: a survey of measurement techniques](http://www.oandplibrary.org/al/1964_01_044.asp). Artificial Limbs, 8, 44-66.
- Kwon Y-H (1998) [BSP Estimation Methods](http://kwon3d.com/theory/bsp.html).
- Ruina A, Rudra P (2013) [Introduction to Statics and Dynamics](http://ruina.tam.cornell.edu/Book/index.html). Oxford University Press.
- Winter DA (2009) [Biomechanics and motor control of human movement](http://books.google.com.br/books?id=_bFHL08IWfwC). 4 ed. Hoboken, EUA: Wiley.
- Yanai T, Wilson BD (2008) [How does buoyancy influence front-crawl performance?
Exploring the assumptions](https://onlinelibrary.wiley.com/doi/pdf/10.1002/jst.23). Sports Technol., 1 2–3, 89–99.
- Zatsiorsky VM (2002) [Kinetics of human motion](http://books.google.com.br/books?id=wp3zt7oF8a0C&lpg=PA571&ots=Kjc17DAl19&dq=ZATSIORSKY%2C%20Vladimir%20M.%20Kinetics%20of%20human%20motion&hl=pt-BR&pg=PP1#v=onepage&q&f=false). Champaign, IL: Human Kinetics.
- Some of the original works on body segment parameters:
  - Contini R (1972) [Body Segment Parameters, Part II](http://www.oandplibrary.org/al/1972_01_001.asp). Artificial Limbs, 16, 1-19.
  - Dempster WT (1955) [Space requirements of the seated operator: geometrical, kinematic, and mechanical aspects of the body, with special reference to the limbs](http://deepblue.lib.umich.edu/handle/2027.42/4540). WADC Technical Report 55-159, AD-087-892, Wright-Patterson Air Force Base, Ohio.
  - Hanavan EP (1964). [A mathematical model of the human body](http://www.dtic.mil/cgi-bin/GetTRDoc?AD=AD0608463). AMRL-TR-64-102, AD-608-463. Aerospace Medical Research Laboratories, Wright-Patterson Air Force Base, Ohio.


# Modeling Volumes and Multiple Compartments

This notebook example provides a basic demonstration on how to create and dynamically simulate multi-compartment models.

Illustrated below is the multi-compartment model utilized in this notebook:

In the above example:
* For metabolite **x**:
    * The biochemical pathway for the conversion of **x** occurs in the `large` compartment outlined by the  dotted black line.
* For metabolite **y**:
    * Cofactor **y** is necessary for the conversion of metabolite **x** in the biochemical pathway.
    * The synthesis of **y** occurs in the `medium` compartment outlined by the blue line.
    * `R_Ytr` is an antiporter, coupling the import of **y2** into the `large` compartment with the export of **y3** to the `medium` compartment.
* For metabolite **z**:
    * Protein **z** is synthesized in the `small` compartment outlined by the red line.
    * Protein **z** is also to facilitate the conversion of **x5** back into **x4** and for metabolic functions outside of the model's scope.

The reaction converting **x5** back to **x4** converted into **x5** The pair of irreversible reactions `R3_X` and `R_XZ` form a cycle that is used to  The synthesis and degradation of metabolite **y** occurs in the **medium compartment** outlined in blue.

COBRApy is currently in the process of developing improved compartment handling. These changes are outlined in the following COBRApy issues:

* https://github.com/opencobra/cobrapy/pull/725
* https://github.com/opencobra/cobrapy/projects/5

MASSpy is awaiting these COBRApy changes in order to improve how compartments are handled in dynamic simulations, SBML compatibity, etc. Once these changes have been implemented in COBRApy, a new version of MASSpy will be developed and released with improved functionality around compartments and their handling.

## Models with Multiple Compartments

```python
import sympy as sym

from mass import (
    MassConfiguration, MassMetabolite, MassModel, MassReaction, Simulation)
from mass.test import create_test_model
from mass.visualization import plot_time_profile
model = create_test_model("MultiCompartment")
```

### Viewing compartments in a model
The `MassModel.compartments` attribute is used to get `dict` with compartment identifiers and their corresponding names.

```python
model.compartments
```

    {'l': 'Large', 'm': 'Medium', 's': 'Small'}

The names for the compartments can be reset or changed by using the `MassModel.compartments` attribute setter method. To reset compartment names, pass an empty dict:

```python
model.compartments = {}
```

To set a new name for a compartment, set a `dict` using the `MassModel.compartments` method with the compartment identifer as the key and the compartment name as the value. Compartments can be set one at a time, or multiple at once:

```python
model.compartments = {"l": "the large compartment"}
print(model.compartments)

model.compartments = {"m": "the medium compartment", "s": "the small compartment"}
print(model.compartments)
```

    {'l': 'the large compartment', 'm': '', 's': ''}
    {'l': 'the large compartment', 'm': 'the medium compartment', 's': 'the small compartment'}

#### Volume units

To get a list of all UnitDefinition(s) that contain a volume base unit, an modified filter that scans the base units can be applied:

```python
def volumes_filter(udef):
    if list(filter(lambda u: u.kind in ["liter","litre"], udef.list_of_units)):
        return True
    return False
print(model.units.query(volumes_filter))
```

    [<UnitDefinition Milliliters "mL" at 0x7ffd39e79710>, <UnitDefinition Concentration "mol_per_mL" at 0x7ffd39e79750>]

### Enabling compartment volumes in rate laws
By default, the ``MassConfiguration.exclude_compartment_volumes_in_rates`` is set as ``True``.

```python
mass_config = MassConfiguration()
print(mass_config.exclude_compartment_volumes_in_rates)
```

    True

Therefore, all automatically generated mass action rate laws do not include the compartment volume:

```python
print(model.reactions.get_by_id("R2_X").rate)
```

    kf_R2_X*x3_l(t)

To enable compartment volumes in rate laws, the ``MassConfiguration.exclude_compartment_volumes_in_rates`` attribute must be set to ``False``.

```python
mass_config.exclude_compartment_volumes_in_rates = False
print(model.reactions.get_by_id("R2_X").rate)
```

    kf_R2_X*volume_l*x3_l(t)

As seen above, volume parameters are added into the rate laws to represent compartment volumes. The volume parameters
have identifiers of format `volume_CID` , with `CID` referring to the compartment identifier (e.g., "l" for large compartment).
For a reaction that crosses compartments, more than one "volume" parameter will appear as a variable in the rate:

```python
for param in model.reactions.get_by_id("R_Ytr").rate.atoms(sym.Symbol):
    if str(param).find("volume") != -1:
        print(param)
```

    volume_l
    volume_m

See the section on [Excluding compartments from rates](../tutorials/global_configuration.ipynb#excluding-compartments-from-rates) in the [Global Configuration](../tutorials/global_configuration.ipynb) tutorial for more information about the ``exclude_compartment_volumes_in_rates`` attribute.

### The "boundary" compartment

In boundary reactions (e.g., pseudeoreactions such as sinks, demands, and exchanges), metabolites that exist in the `boundary` a.k,a. the boundary conditions, are given a default "boundary" compartment with the identifier "b". This compartment is treated as a pseudo-compartment, and therefore the 'boundary' metabolites are treated as pseudo-metabolites, meaning no corresponding object is created for them.

Boundary metabolites can be accessed either through the `MassReaction.boundary_metabolite` method.

```python
x1_b = model.reactions.get_by_id("SK_x1_l").boundary_metabolite
x1_b
```

    'x1_b'

If a reaction is not a boundary reaction (i.e., `MassReaction.boundary==False`) then `None` will be returned:

```python
print(model.reactions.get_by_id("R_Ytr").boundary_metabolite)
```

    None

The `boundary_metabolite` attribute is useful for getting and setting values in the `MassModel.boundary_conditions` attribute.

```python
model.boundary_conditions[x1_b] = 2
model.boundary_conditions
```

    {'x1_b': 2}

To change the 'boundary' compartment identifier and name, a `dict` is passed to the `MassConfiguration.boundary_compartment` attribute setter:

```python
print("Before: {0}\n{1}".format(mass_config.boundary_compartment, model.boundary_metabolites))
mass_config.boundary_compartment = {"xt": "External compartment"}
print("\nAfter: {0}\n{1}".format(mass_config.boundary_compartment, model.boundary_metabolites))
```

    Before: {'b': 'boundary'}
    ['x1_b', 'x5_b', 'y1_b', 'y4_b', 'z1_b', 'z2_b']

    After: {'xt': 'External compartment'}
    ['x1_xt', 'x5_xt', 'y1_xt', 'y4_xt', 'z1_xt', 'z2_xt']

The "boundary" compartment is automatically assumed to have a volume of 1, and therefore is not factored in the rate laws. It is also ignored by the `MassModel.compartments` attribute, even when explicitly set:

```python
for r in model.sinks:
    print("{0}: {1}".format(r.id, r.get_mass_action_rate()))
model.compartments = {"xt": "External compartment"}
model.compartments
```

    SK_y1_m: kf_SK_y1_m*y1_xt
    SK_z1_s: kf_SK_z1_s*z1_xt

    {'l': 'the large compartment',
     'm': 'the medium compartment',
     's': 'the small compartment'}

See the section on [For compartments and SBML](../tutorials/global_configuration.ipynb#for-compartments-and-SBML) in the [Global Configuration](../tutorials/global_configuration.ipynb) tutorial for more information about the ``boundary_compartment`` attribute.

The 'boundary' pseudo-compartment and 'boundary' pseudo-metabolites are designed to make working with boundary conditions convenient at the cost of having finer user control. This primarily useful for
* Setting functions as boundary conditions (e.g., an oscillating function for external oxygen concentration)
* Using custom rates to set fixed inputs, causing irrelevant boundary conditions to be ignored altogether.

However, for finer control over external compartment and boundary conditions (and general best practices for SBML compatibility in MASSpy), it is recommended to (1) create new `MassMetabolite` objects, define their `compartment` and `initial_condition` attributes, (2) set the  `fixed` attribute as `True`, and (3) add the metabolites to the appropriate reactions. This ensures the concentration of the metabolite is fixed at a constant value, and that its initial condition value is treated as a boundary condition.

#### Fixed inputs
To bypass using the 'boundary' pseudo-compartment, it is recommended to set a fixed input using a custom rate law:

```python
for r in model.reactions.get_by_any(["SK_x1_l", "SK_y1_m", "SK_z1_s"]):
    model.add_custom_rate(r, custom_rate=r.kf_str)
    print("{0}: {1}".format(r.id, r.rate))
```

    SK_x1_l: kf_SK_x1_l
    SK_y1_m: kf_SK_y1_m
    SK_z1_s: kf_SK_z1_s

## Getting and setting compartment volumes
Support for compartment volumes is currently through the `MassModel.custom_parameters` attribute. To view what compartment volumes are set:

```python
def volume_filter(parameter):
    if str(parameter).startswith("volume"):
        return True
    return False

for vol_id in filter(volume_filter, model.custom_parameters):
    print("{0}: {1}".format(vol_id, model.custom_parameters[vol_id]))
```

    volume_l: 10.0
    volume_m: 5.0
    volume_s: 1.0

To set or change a compartment volume, the value in the `MassModel.custom_parameters` dict is set using the volume parameter ID as the key:

```python
# Set the large compartment volume to 15
model.custom_parameters["volume_l"] = 15

# Double current medium compartment volume
model.custom_parameters["volume_m"] = model.custom_parameters["volume_m"] * 2

# 10% decrease to current small compartment volume
model.custom_parameters["volume_s"] = model.custom_parameters["volume_s"] * (1 + (-10/100))

for vol_id in filter(volume_filter, model.custom_parameters):
    print("{0}: {1}".format(vol_id, model.custom_parameters[vol_id]))
```

    volume_l: 15
    volume_m: 10.0
    volume_s: 0.9

## Simulating with Volumes and Multiple Compartments
Using a newly loaded model, the following section provides guidance on dynamic simulations for models with multiple compartments and includes examples on perturbing compartment volume.

```python
# Ensure compartments are active and boundary compartment is reset
mass_config.exclude_compartment_volumes_in_rates = False
mass_config.boundary_compartment = {'b': 'boundary'}

# Start with a fresh model, checking to ensure compartment volumes are reset
model = create_test_model("MultiCompartment")
for vol_id in filter(volume_filter, model.custom_parameters):
    print("{0}: {1}".format(vol_id, model.custom_parameters[vol_id]))
```

    volume_l: 10.0
    volume_m: 5.0
    volume_s: 1.0

As always, a model must first must be loaded into the `mass.Simulation`. object in order to run a simulation. A quick simulation shows that the model is already at a steady state:

```python
simulation = Simulation(model, verbose=True)
conc_sol = simulation.simulate(model, time=(0, 1000))[0]
plot_time_profile(conc_sol, plot_function="loglog", legend="right outside")
```

A volume parameter can be perturbed just like any other parameter using a `dict`. For example, suppose volume of the large compartment `volume_m` lost 40% of its volume:

```python
conc_sol = simulation.simulate(model, time=(0, 1000), perturbations={
    "volume_m": "volume_m * (1 - 0.4)",
})[0]
plot_time_profile(conc_sol, plot_function="loglog", legend="right outside")
```

Note that in the above simulation, several of the metabolite concentrations in the large compartment changed. The `observable` argument can be used with the `MassMetabolite.compartment` attribute to look at metabolites in a specific compartment for futher examination

```python
plot_time_profile(conc_sol, observable=list(model.metabolites.query(lambda m: m.compartment == "m")),
                  plot_function="semilogx", legend="right outside")
```

Multiple volumes also can be perturbed simultaneously. For example, suppose 1 mL of fluid from the `large` compartment was transfered to the `small` compartment, while 1.5 mL was transfered to the `medium` compartment:

```python
conc_sol = simulation.simulate(model, time=(0, 1000), perturbations={
    "volume_l": "volume_l - 2.5",
    "volume_m": "volume_m + 1.5",
    "volume_s": "volume_s + 1.0"
})[0]
plot_time_profile(conc_sol, plot_function="loglog", legend="right outside")
```

Helpful tips: When enabling compartment volumes, it is up to the user to track their units to ensure that no numerical consistency issues arise. To make this a bit easier, be aware of the following MASSpy expectations and behaviors:

* When compartment volumes are disabled, MASSpy expects that volumes are already factored into initial condition values, and therefore considers values to be initial concentrations. Consequently, metabolite solutions returned by solutions will be for metabolite concentrations (e.g., mol/L, g/cDW)

* When compartment volumes are enabled, MASSpy expects that volumes have not been factored factored into initial condition values, and therefore considers values to be initial amounts.
    Consequently, metabolite solutions returned by solutions will be for metabolite amounts (e.g., mol, grams)


Gimlet Potential
================

sep 23, 2019 yuanqing

# Introduction

We propose a flavor of machine learning potential where we preserve the traditional functional form of a Molecular Mechanics (MM) potential where the energy is the sum of bond, angle, torsion, and non-bonded terms, which are functions of the corresponding bond length, angles, dihedral angles, and distances, respectively.

\begin{equation}
E = \sum\limits_{r} E_\text{bonded}(r) + \sum\limits_{\theta}E_\text{angle}(\theta) + \sum\limits_{\phi}E_\text{torsion}(\phi) + \sum\limits_{l} E_\text{non-bonded}(l).
\end{equation}

The difference is, however, that for any of these terms, rather than using a harmonic or polynomial expression, we design a graph net to parameterize a _functional_ that takes the numeric value of $r, \theta, \phi, l$ and outputs the energy contribution.

# Prep
boring stuff.
please, please, please don't look inside.

```
! wget https://s3-us-west-1.amazonaws.com/deepchem.io/datasets/molnet_publish/qm9.zip
! unzip qm9.zip
```

    --2019-09-24 18:39:34--  https://s3-us-west-1.amazonaws.com/deepchem.io/datasets/molnet_publish/qm9.zip
    Resolving s3-us-west-1.amazonaws.com (s3-us-west-1.amazonaws.com)... 52.219.116.152
    Connecting to s3-us-west-1.amazonaws.com (s3-us-west-1.amazonaws.com)|52.219.116.152|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 44827467 (43M) [application/zip]
    Saving to: ‘qm9.zip’

    qm9.zip             100%[===================>]  42.75M  14.4MB/s    in 3.0s

    2019-09-24 18:39:38 (14.4 MB/s) - ‘qm9.zip’ saved [44827467/44827467]

    Archive:  qm9.zip
      inflating: gdb9.sdf
      inflating: gdb9.sdf.csv
      inflating: QM9_README

```
! pip install tensorflow==2.0.0-beta1
! pip uninstall gin-config -y
! rm -rf gimlet
! git clone https://github.com/choderalab/gimlet.git
```

    Collecting tensorflow==2.0.0-beta1
      Downloading https://files.pythonhosted.org/packages/29/6c/2c9a5c4d095c63c2fb37d20def0e4f92685f7aee9243d6aae25862694fd1/tensorflow-2.0.0b1-cp36-cp36m-manylinux1_x86_64.whl (87.9MB)
         |████████████████████████████████| 87.9MB 30.6MB/s
    Collecting tb-nightly<1.14.0a20190604,>=1.14.0a20190603 (from tensorflow==2.0.0-beta1)
      Downloading https://files.pythonhosted.org/packages/a4/96/571b875cd81dda9d5dfa1422a4f9d749e67c0a8d4f4f0b33a4e5f5f35e27/tb_nightly-1.14.0a20190603-py3-none-any.whl (3.1MB)
         |████████████████████████████████| 3.1MB 31.7MB/s
    Requirement already satisfied: astor>=0.6.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (0.8.0)
    Requirement already satisfied: keras-applications>=1.0.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.0.8)
    Requirement already satisfied: gast>=0.2.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (0.2.2)
    Requirement already satisfied: keras-preprocessing>=1.0.5 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.1.0)
    Requirement already satisfied: wheel>=0.26 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (0.33.6)
    Requirement already satisfied: wrapt>=1.11.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.11.2)
    Requirement already satisfied: numpy<2.0,>=1.14.5 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.16.5)
    Requirement already satisfied: six>=1.10.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.12.0)
    Requirement already satisfied: protobuf>=3.6.1 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (3.7.1)
    Collecting tf-estimator-nightly<1.14.0.dev2019060502,>=1.14.0.dev2019060501 (from tensorflow==2.0.0-beta1)
      Downloading https://files.pythonhosted.org/packages/32/dd/99c47dd007dcf10d63fd895611b063732646f23059c618a373e85019eb0e/tf_estimator_nightly-1.14.0.dev2019060501-py2.py3-none-any.whl (496kB)
         |████████████████████████████████| 501kB 26.0MB/s
    Requirement already satisfied: grpcio>=1.8.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.15.0)
    Requirement already satisfied: google-pasta>=0.1.6 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (0.1.7)
    Requirement already satisfied: termcolor>=1.1.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (1.1.0)
    Requirement already satisfied: absl-py>=0.7.0 in /usr/local/lib/python3.6/dist-packages (from tensorflow==2.0.0-beta1) (0.8.0)
    Requirement already satisfied: markdown>=2.6.8 in /usr/local/lib/python3.6/dist-packages (from tb-nightly<1.14.0a20190604,>=1.14.0a20190603->tensorflow==2.0.0-beta1) (3.1.1)
    Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/lib/python3.6/dist-packages (from tb-nightly<1.14.0a20190604,>=1.14.0a20190603->tensorflow==2.0.0-beta1) (0.15.6)
    Requirement already satisfied: setuptools>=41.0.0 in /usr/local/lib/python3.6/dist-packages (from tb-nightly<1.14.0a20190604,>=1.14.0a20190603->tensorflow==2.0.0-beta1) (41.2.0)
    Requirement already satisfied: h5py in /usr/local/lib/python3.6/dist-packages (from keras-applications>=1.0.6->tensorflow==2.0.0-beta1) (2.8.0)
    Installing collected packages: tb-nightly, tf-estimator-nightly, tensorflow
      Found existing installation: tensorflow 1.14.0
        Uninstalling tensorflow-1.14.0:
          Successfully uninstalled tensorflow-1.14.0
    Successfully installed tb-nightly-1.14.0a20190603 tensorflow-2.0.0b1 tf-estimator-nightly-1.14.0.dev2019060501
    Uninstalling gin-config-0.2.1:
      Successfully uninstalled gin-config-0.2.1
    Cloning into 'gimlet'...
    remote: Enumerating objects: 106, done.
    remote: Counting objects: 100% (106/106), done.
    remote: Compressing objects: 100% (77/77), done.
    remote: Total 2118 (delta 62), reused 64 (delta 28), pack-reused 2012
    Receiving objects: 100% (2118/2118), 9.31 MiB | 5.89 MiB/s, done.
    Resolving deltas: 100% (1495/1495), done.

```
import sys
sys.path.append('/content/gimlet')
from sklearn import metrics
import os

import tensorflow as tf
import gin
import lime
import pandas as pd
import numpy as np
```

```
mols_ds = gin.i_o.from_sdf.to_ds('gdb9.sdf', has_charge=False)

attr_ds = pd.read_csv('gdb9.sdf.csv').values[:, 1:].astype(np.float32)

attr_ds = attr_ds / np.linalg.norm(attr_ds, axis=0) - np.std(attr_ds, axis=0)

attr_ds = tf.data.Dataset.from_tensor_slices(attr_ds)

ds = tf.data.Dataset.zip((mols_ds, attr_ds))

ds = ds.take(10240)

ds = ds.map(
    lambda mol, attr: (mol[0], mol[1], mol[2], attr))

ds = gin.probabilistic.gn_hyper.HyperGraphNet.batch(
    ds,
    128,
    attr_dimension=19).cache(
        str(os.getcwd()) + 'temp')

n_batches = int(gin.probabilistic.gn.GraphNet.get_number_batches(ds))
n_te = n_batches // 10

ds_te = ds.take(n_te)
ds_vl = ds.skip(n_te).take(n_te)
ds_tr = ds.skip(2 * n_te)
```

    WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/array_ops.py:1340: add_dispatch_support.<locals>.wrapper (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
    Instructions for updating:
    Use tf.where in 2.0, which has the same broadcast rule as np.where
    WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int64
    WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int64
    WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int64
    WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int64
    WARNING:tensorflow:The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int64

```
pd.read_csv('gdb9.sdf.csv')
```

      mol_id
      A
      B
      C
      mu
      alpha
      homo
      lumo
      gap
      r2
      zpve
      u0
      u298
      h298
      g298
      cv
      u0_atom
      u298_atom
      h298_atom
      g298_atom

      0
      gdb_1
      157.71180
      157.709970
      157.706990
      0.0000
      13.21
      -0.3877
      0.1171
      0.5048
      35.3641
      0.044749
      -40.478930
      -40.476062
      -40.475117
      -40.498597
      6.469
      -395.999595
      -398.643290
      -401.014647
      -372.471772

      1
      gdb_2
      293.60975
      293.541110
      191.393970
      1.6256
      9.46
      -0.2570
      0.0829
      0.3399
      26.1563
      0.034358
      -56.525887
      -56.523026
      -56.522082
      -56.544961
      6.316
      -276.861363
      -278.620271
      -280.399259
      -259.338802

      2
      gdb_3
      799.58812
      437.903860
      282.945450
      1.8511
      6.31
      -0.2928
      0.0687
      0.3615
      19.0002
      0.021375
      -76.404702
      -76.401867
      -76.400922
      -76.422349
      6.002
      -213.087624
      -213.974294
      -215.159658
      -201.407171

      3
      gdb_4
      0.00000
      35.610036
      35.610036
      0.0000
      16.28
      -0.2845
      0.0506
      0.3351
      59.5248
      0.026841
      -77.308427
      -77.305527
      -77.304583
      -77.327429
      8.574
      -385.501997
      -387.237686
      -389.016047
      -365.800724

      4
      gdb_5
      0.00000
      44.593883
      44.593883
      2.8937
      12.99
      -0.3604
      0.0191
      0.3796
      48.7476
      0.016601
      -93.411888
      -93.409370
      -93.408425
      -93.431246
      6.278
      -301.820534
      -302.906752
      -304.091489
      -288.720028

      5
      gdb_6
      285.48839
      38.982300
      34.298920
      2.1089
      14.18
      -0.2670
      -0.0406
      0.2263
      59.9891
      0.026603
      -114.483613
      -114.480746
      -114.479802
      -114.505268
      6.413
      -358.756935
      -360.512706
      -362.291066
      -340.464421

      6
      gdb_7
      80.46225
      19.906490
      19.906330
      0.0000
      23.95
      -0.3385
      0.1041
      0.4426
      109.5031
      0.074542
      -79.764152
      -79.760666
      -79.759722
      -79.787269
      10.098
      -670.788296
      -675.710476
      -679.860821
      -626.927299

      7
      gdb_8
      127.83497
      24.858720
      23.978720
      1.5258
      16.97
      -0.2653
      0.0784
      0.3437
      83.7940
      0.051208
      -115.679136
      -115.675816
      -115.674872
      -115.701876
      8.751
      -481.106758
      -484.355372
      -487.319724
      -450.124128

      8
      gdb_9
      160.28041
      8.593230
      8.593210
      0.7156
      28.78
      -0.2609
      0.0613
      0.3222
      177.1963
      0.055410
      -116.609549
      -116.605550
      -116.604606
      -116.633775
      12.482
      -670.268091
      -673.980434
      -677.537155
      -631.346845

      9
      gdb_10
      159.03567
      9.223270
      9.223240
      3.8266
      24.45
      -0.3264
      0.0376
      0.3640
      160.7223
      0.045286
      -132.718150
      -132.714563
      -132.713619
      -132.742149
      10.287
      -589.812024
      -592.893721
      -595.857446
      -557.125708

      10
      gdb_11
      57.22434
      10.111220
      9.073680
      2.5682
      25.11
      -0.2540
      -0.0198
      0.2342
      166.9728
      0.055355
      -153.787612
      -153.783728
      -153.782784
      -153.812518
      11.219
      -645.328373
      -649.112252
      -652.668973
      -606.577810

      11
      gdb_12
      73.84720
      11.347930
      9.836390
      3.7286
      21.57
      -0.2543
      0.0302
      0.2845
      145.3078
      0.045279
      -169.860788
      -169.856903
      -169.855958
      -169.885594
      10.890
      -542.642800
      -545.536872
      -548.499969
      -510.206860

      12
      gdb_13
      29.45018
      8.377010
      7.420760
      0.0597
      34.75
      -0.3230
      0.0949
      0.4179
      227.1361
      0.103182
      -119.052475
      -119.047927
      -119.046983
      -119.078157
      14.840
      -947.522902
      -954.444954
      -960.373659
      -882.773386

      13
      gdb_14
      35.09545
      9.368600
      8.149700
      1.4131
      27.87
      -0.2619
      0.0798
      0.3417
      193.1659
      0.079754
      -154.972731
      -154.968412
      -154.967467
      -154.998148
      13.546
      -761.149592
      -766.437610
      -771.179696
      -709.348724

      14
      gdb_15
      39.37691
      10.040330
      8.903530
      1.1502
      28.13
      -0.2525
      0.0910
      0.3435
      187.1015
      0.079534
      -154.960361
      -154.956045
      -154.955100
      -154.985747
      12.934
      -753.387305
      -758.677206
      -763.419292
      -701.566985

      15
      gdb_16
      20.15852
      20.153020
      12.589100
      0.0005
      30.82
      -0.2888
      0.1042
      0.3930
      155.8145
      0.081231
      -117.824798
      -117.821426
      -117.820482
      -117.849087
      11.041
      -804.996156
      -810.879052
      -815.621765
      -752.743481

      16
      gdb_17
      25.77385
      22.121090
      14.180660
      1.7675
      24.04
      -0.2682
      0.1042
      0.3724
      129.8910
      0.057289
      -153.742562
      -153.739410
      -153.738466
      -153.766642
      9.176
      -617.059093
      -621.302308
      -624.859029
      -577.790207

      17
      gdb_18
      10.12193
      8.490110
      4.896150
      2.7362
      35.53
      -0.2431
      -0.0087
      0.2344
      292.4367
      0.083382
      -193.088340
      -193.082969
      -193.082024
      -193.116476
      16.893
      -929.847229
      -935.364288
      -940.698742
      -870.625439

      18
      gdb_19
      10.78886
      9.275090
      5.149770
      3.6367
      31.83
      -0.2436
      0.0347
      0.2783
      267.6148
      0.073190
      -209.159302
      -209.154020
      -209.153076
      -209.187468
      16.561
      -825.772351
      -830.456078
      -835.198164
      -772.946761

      19
      gdb_20
      11.14750
      10.363880
      5.420910
      3.4869
      28.07
      -0.2495
      0.0556
      0.3051
      244.2308
      0.063824
      -225.221461
      -225.217075
      -225.216131
      -225.247724
      15.292
      -716.173512
      -720.530307
      -724.679396
      -668.531146

      20
      gdb_21
      7.75166
      7.748470
      4.486680
      0.0897
      45.46
      -0.3167
      0.0843
      0.4010
      355.0621
      0.131146
      -158.342346
      -158.336603
      -158.335658
      -158.370016
      20.273
      -1225.228893
      -1234.067357
      -1241.773795
      -1139.228784

      21
      gdb_22
      8.67553
      8.005680
      4.755420
      1.4259
      38.58
      -0.2612
      0.0740
      0.3351
      318.3721
      0.107673
      -194.267232
      -194.261748
      -194.260804
      -194.294663
      19.052
      -1041.760949
      -1048.984205
      -1055.505279
      -968.725804

      22
      gdb_23
      0.00000
      4.425973
      4.425973
      0.0000
      38.52
      -0.2599
      -0.0214
      0.2386
      278.6264
      0.037354
      -153.459846
      -153.455442
      -153.454498
      -153.482621
      15.312
      -672.822680
      -675.392957
      -678.356054
      -637.234762

      23
      gdb_24
      0.00000
      4.579322
      4.579322
      3.7920
      32.66
      -0.3102
      -0.0543
      0.2559
      260.1896
      0.027259
      -169.557758
      -169.553764
      -169.552820
      -169.581024
      12.930
      -585.659170
      -587.597545
      -589.967647
      -556.756733

      24
      gdb_25
      0.00000
      4.732690
      4.732690
      0.0023
      27.70
      -0.3696
      -0.0926
      0.2770
      242.9308
      0.015951
      -185.648533
      -185.644825
      -185.643880
      -185.667652
      10.398
      -494.017128
      -495.245791
      -497.022269
      -468.889785

      25
      gdb_26
      68.51710
      4.834500
      4.515860
      2.7824
      31.14
      -0.2777
      -0.0735
      0.2042
      268.3921
      0.037208
      -190.624631
      -190.620363
      -190.619419
      -190.650543
      13.049
      -639.550898
      -642.205888
      -645.168986
      -605.675452

      26
      gdb_27
      67.88408
      5.008230
      4.664130
      2.3112
      26.25
      -0.3166
      -0.1100
      0.2066
      251.0007
      0.026540
      -206.721858
      -206.717875
      -206.716931
      -206.747625
      11.329
      -551.957544
      -553.902194
      -556.272296
      -524.368483

      27
      gdb_28
      56.25048
      4.774410
      4.400880
      0.0020
      26.12
      -0.2668
      -0.1113
      0.1555
      266.8164
      0.036943
      -227.798785
      -227.794570
      -227.793626
      -227.825074
      12.147
      -612.158247
      -614.845868
      -617.808966
      -578.263349

      28
      gdb_29
      80.28766
      3.368800
      3.368800
      0.0000
      42.32
      -0.2412
      0.0684
      0.3096
      400.2236
      0.083896
      -155.908941
      -155.903180
      -155.902236
      -155.937641
      17.447
      -953.948594
      -959.221553
      -964.556634
      -895.336744

      29
      gdb_30
      27.39459
      4.530050
      4.083420
      0.7067
      40.09
      -0.2592
      0.0566
      0.3157
      333.9589
      0.084338
      -155.897345
      -155.892291
      -155.891347
      -155.924226
      17.130
      -946.672000
      -952.388607
      -957.723689
      -886.918711

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      133855
      gdb_133856
      3.39337
      1.652170
      1.377610
      1.4779
      81.04
      -0.2185
      -0.0383
      0.1802
      948.1845
      0.135036
      -384.576055
      -384.568668
      -384.567724
      -384.607015
      30.795
      -1716.309906
      -1726.784914
      -1736.267830
      -1599.853648

      133856
      gdb_133857
      3.60121
      1.656720
      1.412820
      1.5261
      85.67
      -0.2014
      -0.0515
      0.1498
      971.5360
      0.158963
      -348.608360
      -348.601028
      -348.600084
      -348.639441
      30.453
      -1872.914817
      -1885.202071
      -1895.870979
      -1743.419550

      133857
      gdb_133858
      3.35113
      1.790160
      1.409290
      1.8057
      92.98
      -0.1693
      -0.1041
      0.0652
      948.9617
      0.144613
      -364.587575
      -364.579951
      -364.579007
      -364.618907
      30.975
      -1711.267871
      -1722.482712
      -1732.558624
      -1588.307483

      133858
      gdb_133859
      3.63247
      1.740150
      1.472840
      0.8183
      82.97
      -0.2044
      -0.0550
      0.1494
      927.4789
      0.147108
      -364.639427
      -364.632258
      -364.631314
      -364.670376
      29.576
      -1743.805468
      -1755.305825
      -1765.381737
      -1620.604743

      133859
      gdb_133860
      3.65328
      1.668730
      1.420420
      0.8087
      78.23
      -0.2092
      -0.0580
      0.1512
      921.5134
      0.135485
      -384.532543
      -384.525501
      -384.524557
      -384.563401
      29.182
      -1689.005734
      -1699.697233
      -1709.180149
      -1572.485471

      133860
      gdb_133861
      3.56693
      1.442380
      1.262920
      1.5690
      79.94
      -0.2228
      0.0828
      0.3056
      1060.6837
      0.172342
      -403.038700
      -403.031118
      -403.030174
      -403.070528
      30.511
      -1857.334396
      -1870.352070
      -1881.613974
      -1721.058267

      133861
      gdb_133862
      2.70455
      1.646100
      1.413640
      2.7921
      80.93
      -0.2206
      0.0723
      0.2929
      1018.8859
      0.171040
      -403.006103
      -402.998386
      -402.997441
      -403.037739
      31.855
      -1836.879485
      -1849.812446
      -1861.073722
      -1700.482874

      133862
      gdb_133863
      2.74553
      2.001510
      1.360020
      0.4506
      91.34
      -0.2008
      -0.0152
      0.1856
      995.0389
      0.183006
      -349.790689
      -349.783068
      -349.782124
      -349.822503
      31.336
      -1986.985286
      -2000.868295
      -2012.723195
      -1844.579021

      133863
      gdb_133864
      2.83041
      1.998400
      1.417810
      1.9977
      91.95
      -0.1386
      -0.0638
      0.0747
      942.7307
      0.158927
      -385.748296
      -385.741180
      -385.740236
      -385.779365
      29.845
      -1824.050064
      -1836.472232
      -1847.141140
      -1694.291243

      133864
      gdb_133865
      2.89460
      2.086960
      1.428180
      2.1480
      88.57
      -0.1325
      -0.0539
      0.0786
      915.8811
      0.146860
      -401.792449
      -401.785191
      -401.784247
      -401.823773
      29.474
      -1703.152297
      -1714.596179
      -1724.672091
      -1579.930865

      133865
      gdb_133866
      2.90067
      2.059270
      1.456750
      2.3806
      84.17
      -0.1575
      -0.0752
      0.0823
      892.5613
      0.135006
      -421.679014
      -421.672181
      -421.671237
      -421.709861
      28.307
      -1644.241752
      -1655.063773
      -1664.546689
      -1527.458562

      133866
      gdb_133867
      3.12173
      1.957720
      1.911430
      1.4371
      78.33
      -0.2350
      0.0942
      0.3291
      846.5551
      0.163548
      -385.826182
      -385.820415
      -385.819470
      -385.855796
      26.177
      -1872.924230
      -1886.192908
      -1896.861188
      -1742.252383

      133867
      gdb_133868
      3.01698
      1.895490
      1.844890
      0.2864
      85.74
      -0.2465
      0.0920
      0.3384
      895.6742
      0.187833
      -349.901681
      -349.895706
      -349.894762
      -349.931420
      27.489
      -2056.633765
      -2071.549654
      -2083.404554
      -1912.925418

      133868
      gdb_133869
      3.09994
      1.965800
      1.893160
      1.4470
      77.78
      -0.2476
      0.0811
      0.3287
      849.7933
      0.163634
      -385.814978
      -385.809206
      -385.808262
      -385.844608
      26.226
      -1865.893619
      -1879.159159
      -1889.828067
      -1735.231812

      133869
      gdb_133870
      3.22990
      2.024540
      1.960070
      1.7397
      70.28
      -0.2505
      0.0710
      0.3215
      801.6841
      0.139275
      -421.741500
      -421.735945
      -421.735001
      -421.770998
      24.855
      -1683.452280
      -1695.076257
      -1704.559173
      -1565.822580

      133870
      gdb_133871
      3.23767
      2.022870
      1.981900
      2.1011
      70.50
      -0.2508
      0.0836
      0.3344
      798.0812
      0.139644
      -421.754627
      -421.749139
      -421.748195
      -421.784075
      24.583
      -1691.689590
      -1703.355610
      -1712.838526
      -1574.028515

      133871
      gdb_133872
      3.17344
      2.015950
      1.966220
      0.8088
      75.03
      -0.2108
      0.0880
      0.2988
      817.6336
      0.151201
      -401.859197
      -401.853493
      -401.852549
      -401.888757
      25.714
      -1745.037268
      -1757.456299
      -1767.532211
      -1620.708910

      133872
      gdb_133873
      3.08572
      1.913100
      1.875260
      1.9005
      81.97
      -0.2086
      0.0824
      0.2910
      871.4877
      0.175911
      -365.922771
      -365.916841
      -365.915897
      -365.952493
      26.965
      -1921.263758
      -1935.318705
      -1946.580609
      -1783.922118

      133873
      gdb_133874
      3.17845
      1.977510
      1.911880
      0.6916
      74.29
      -0.2264
      0.0641
      0.2906
      827.7783
      0.151586
      -401.835917
      -401.830161
      -401.829217
      -401.865555
      25.762
      -1730.428859
      -1742.815259
      -1752.891171
      -1606.149446

      133874
      gdb_133875
      3.51898
      2.150950
      1.876170
      2.3177
      73.27
      -0.2283
      0.0744
      0.3027
      785.0566
      0.139289
      -384.596376
      -384.591020
      -384.590076
      -384.625639
      24.482
      -1729.061516
      -1740.810995
      -1750.293911
      -1611.540376

      133875
      gdb_133876
      3.59419
      2.113250
      1.874250
      1.5614
      77.39
      -0.2346
      0.0878
      0.3224
      802.5517
      0.152397
      -364.732075
      -364.726719
      -364.725775
      -364.761334
      24.517
      -1801.942922
      -1814.580953
      -1824.656865
      -1677.681707

      133876
      gdb_133877
      3.45974
      2.118880
      1.844500
      0.2462
      81.37
      -0.2499
      0.1023
      0.3523
      825.8430
      0.164037
      -348.696763
      -348.691301
      -348.690357
      -348.726069
      25.376
      -1928.388495
      -1941.849191
      -1952.518099
      -1797.779400

      133877
      gdb_133878
      3.53569
      2.169980
      1.874680
      1.6747
      77.35
      -0.2301
      0.0938
      0.3239
      800.3447
      0.152293
      -364.737867
      -364.732498
      -364.731554
      -364.767136
      24.515
      -1805.577454
      -1818.207328
      -1828.283240
      -1681.322514

      133878
      gdb_133879
      3.52243
      2.185850
      1.902520
      1.4529
      77.63
      -0.2229
      0.0977
      0.3206
      796.9713
      0.151939
      -364.733608
      -364.728220
      -364.727276
      -364.762857
      24.820
      -1802.904893
      -1815.522844
      -1825.598756
      -1678.637403

      133879
      gdb_133880
      3.56068
      2.195510
      1.920670
      1.3774
      73.24
      -0.2427
      0.0904
      0.3331
      777.3913
      0.139809
      -384.609506
      -384.604306
      -384.603362
      -384.638652
      23.968
      -1737.300710
      -1749.148080
      -1758.630996
      -1619.706151

      133880
      gdb_133881
      3.59483
      2.198990
      1.904230
      1.6637
      69.37
      -0.2254
      0.0588
      0.2842
      760.7472
      0.127406
      -400.633868
      -400.628599
      -400.627654
      -400.663098
      23.658
      -1603.983913
      -1614.898804
      -1623.788097
      -1492.819438

      133881
      gdb_133882
      3.65648
      2.142370
      1.904390
      1.2976
      69.52
      -0.2393
      0.0608
      0.3002
      762.6354
      0.127495
      -400.629713
      -400.624444
      -400.623500
      -400.658942
      23.697
      -1601.376613
      -1612.291504
      -1621.181424
      -1490.211511

      133882
      gdb_133883
      3.67118
      2.143140
      1.895010
      1.2480
      73.60
      -0.2233
      0.0720
      0.2953
      780.3553
      0.140458
      -380.753918
      -380.748619
      -380.747675
      -380.783148
      23.972
      -1667.045429
      -1678.830048
      -1688.312964
      -1549.143391

      133883
      gdb_133884
      3.52845
      2.151310
      1.865820
      1.9576
      77.40
      -0.2122
      0.0881
      0.3003
      803.1904
      0.152222
      -364.720374
      -364.714974
      -364.714030
      -364.749650
      24.796
      -1794.600439
      -1807.210860
      -1817.286772
      -1670.349892

      133884
      gdb_133885
      3.64015
      2.217640
      1.937930
      0.8626
      69.48
      -0.2316
      0.0742
      0.3058
      756.3557
      0.127862
      -400.633052
      -400.627892
      -400.626948
      -400.662186
      23.434
      -1603.471865
      -1614.455155
      -1623.345075
      -1492.247150

133885 rows × 20 columns

# Architecture

```
config_space = {
    'D_V': [16, 32, 64, 128, 256],
    'D_E': [16, 32, 64, 128, 256],
    'D_A': [16, 32, 64, 128, 256],
    'D_T': [16, 32, 64, 128, 256],
    'D_U': [16, 32, 64, 128, 256],

    'phi_e_0': [32, 64, 128],
    'phi_e_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'phi_e_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'phi_v_0': [32, 64, 128],
    'phi_v_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'phi_v_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'phi_a_0': [32, 64, 128],
    'phi_a_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'phi_a_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'phi_t_0': [32, 64, 128],
    'phi_t_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'phi_t_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'phi_u_0': [32, 64, 128],
    'phi_u_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'phi_u_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'f_e_0': [32, 64, 128],
    'f_e_a_0': ['elu', 'relu', 'tanh', 'sigmoid'],
    'f_e_a_1': ['elu', 'relu', 'tanh', 'sigmoid'],

    'f_r': [32, 64, 128],
    'f_r_a': ['elu', 'relu', 'tanh', 'sigmoid'],

    'learning_rate': [1e-5, 1e-4, 1e-3]

}

point = dict(zip(config_space.keys(), [value[0] for value in config_space.values()]))
```

```
class f_v(tf.keras.Model):
    """ Featurization of nodes.
    Here we simply featurize atoms using one-hot encoding.

    """
    def __init__(self, units=point['D_V']):
        super(f_v, self).__init__()
        self.d = tf.keras.layers.Dense(units)

    @tf.function
    def call(self, x):
        x = tf.one_hot(x, 10)
        # set shape because Dense doesn't like variation
        x.set_shape([None, 10])
        return self.d(x)
```

```
class f_r(tf.keras.Model):
    """ Readout function
    """
    def __init__(self, units=point['f_r'], f_r_a=point['f_r_a']):
        super(f_r, self).__init__()
        self.d_k = tf.keras.layers.Dense(units, activation='tanh')
        self.d_q = tf.keras.layers.Dense(units, activation='tanh')
        self.d_pair_0 = tf.keras.layers.Dense(units, activation=f_r_a)
        self.d_pair_1 = tf.keras.layers.Dense(16)

        self.d_e_1 = tf.keras.layers.Dense(16)
        self.d_e_0 = tf.keras.layers.Dense(units, activation='tanh')

        self.d_a_1 = tf.keras.layers.Dense(16)
        self.d_a_0 = tf.keras.layers.Dense(units, activation='tanh')

        self.d_t_1 = tf.keras.layers.Dense(16)
        self.d_t_0 = tf.keras.layers.Dense(units, activation='tanh')

        self.units = units
        self.d_v = point['D_V']
        self.d_e = point['D_E']
        self.d_a = point['D_A']
        self.d_t = point['D_T']
        self.d_u = point['D_U']

    # @tf.function
    def call(self, h_v, h_e, h_a, h_t, h_u,
        h_v_history, h_e_history, h_a_history,
        h_t_history, h_u_history,
        atom_in_mol, bond_in_mol, angle_in_mol, torsion_in_mol,
        adjacency_map, coordinates):

        adjacency_map_full = tf.math.add(
            tf.transpose(
                adjacency_map),
            adjacency_map)

        per_mol_mask = tf.matmul(
            tf.where(
                atom_in_mol,
                tf.ones_like(atom_in_mol, dtype=tf.float32),
                tf.zeros_like(atom_in_mol, dtype=tf.float32)),
            tf.transpose(
                tf.where(
                    atom_in_mol,
                    tf.ones_like(atom_in_mol, dtype=tf.float32),
                    tf.zeros_like(atom_in_mol, dtype=tf.float32))))

        # get distance matrix
        distance = gin.deterministic.md.get_distance_matrix(coordinates)

        distance = tf.expand_dims(
            distance,
            2)

        n_atoms = tf.shape(distance, tf.int64)[0]

        # (n_atoms, n_atoms, units)
        k = tf.multiply(
            tf.tile(
                tf.expand_dims(
                    per_mol_mask,
                    2),
                [1, 1, self.units]),
            tf.tile(
                tf.expand_dims(
                    self.d_k(h_v),
                    1),
                [1, n_atoms, 1]))

        # (n_atoms, n_atoms, units)
        q = tf.multiply(
            tf.tile(
                tf.expand_dims(
                    per_mol_mask,
                    2),
                [1, 1, self.units]),
            tf.tile(
                tf.expand_dims(
                    self.d_q(h_v),
                    0),
                [n_atoms, 1, 1]))

        h_pair = tf.concat(
            [
                k,
                q,
            ],
            axis=2)

        h_pair = tf.math.multiply(
            tf.tile(
                tf.expand_dims(
                    tf.math.multiply(
                        per_mol_mask,
                        tf.where(
                            tf.equal(
                                adjacency_map_full,
                                tf.constant(0, dtype=tf.float32)),
                            tf.ones_like(adjacency_map),
                            tf.zeros_like(adjacency_map))),
                    2),
                [1, 1, 16]),
            self.d_pair_1(self.d_pair_0(h_pair)))

        y_pair = h_pair

        y_a = self.d_a_1(
            self.d_a_0(
                tf.reshape(
                    h_a_history,
                    [
                        tf.shape(h_a_history)[0],
                        -1
                    ])))

        y_e = self.d_e_1(
            self.d_e_0(
                tf.reshape(
                    h_e_history,
                    [
                        tf.shape(h_e_history)[0],
                        -1
                    ])))

        y_t = self.d_t_1(
            self.d_t_0(
                tf.reshape(
                    h_t_history,
                    [
                        tf.shape(h_t_history)[0],
                        -1
                    ])))

        return y_e, y_a, y_t, y_pair

```

```
gn = gin.probabilistic.gn_hyper.HyperGraphNet(
    f_e=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['f_e_0'], point['f_e_a_0'], point['D_E'], point['f_e_a_1'])),
    f_a=tf.keras.layers.Dense(point['D_A']),
    f_t=tf.keras.layers.Dense(point['D_T']),
    f_v=f_v(),
    f_u=(lambda atoms, adjacency_map, batched_attr_in_mol: \
        tf.tile(
            tf.zeros((1, point['D_U'])),
            [
                 tf.math.count_nonzero(batched_attr_in_mol),
                 1
            ]
        )),
    phi_e=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['phi_e_0'], point['phi_e_a_0'], point['D_E'], point['phi_e_a_1'])),
    phi_u=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['phi_u_0'], point['phi_u_a_0'], point['D_U'], point['phi_u_a_1'])),
    phi_v=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['phi_v_0'], point['phi_v_a_0'], point['D_V'], point['phi_v_a_1'])),
    phi_a=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['phi_a_0'], point['phi_a_a_0'], point['D_A'], point['phi_a_a_1'])),
    phi_t=lime.nets.for_gn.ConcatenateThenFullyConnect(
        (point['phi_t_0'], point['phi_t_a_0'], point['D_T'], point['phi_t_a_1'])),
    f_r=f_r(),
    repeat=5)
```

```
optimizer = tf.keras.optimizers.Adam(1e-5)
```

```
for dummy_idx in range(10):
    for atoms, adjacency_map, coordinates, attr, atom_in_mol, attr_in_mol in ds_tr:
        with tf.GradientTape() as tape:
            y_e, y_a, y_t, y_pair = gn(
                atoms, adjacency_map, coordinates, atom_in_mol, attr_in_mol)

            bond_idxs, angle_idxs, torsion_idxs = gin.probabilistic.gn_hyper\
                .get_geometric_idxs(atoms, adjacency_map)

            is_bond = tf.greater(
                adjacency_map,
                tf.constant(0, dtype=tf.float32))

            distance_matrix = gin.deterministic.md.get_distance_matrix(
                coordinates)

            bond_distances = tf.boolean_mask(
                distance_matrix,
                is_bond)

            angle_angles = gin.deterministic.md.get_angles(
                coordinates,
                angle_idxs)

            torsion_dihedrals = gin.deterministic.md.get_dihedrals(
                coordinates,
                torsion_idxs)

            u_bond = tf.math.polyval(
                tf.transpose(y_e),
                bond_distances)

            u_angle = tf.math.polyval(
                tf.transpose(y_a),
                angle_angles)

            u_dihedral = tf.math.polyval(
                tf.transpose(y_t),
                torsion_dihedrals)

            u_pair = tf.math.polyval(
                tf.transpose(y_pair),
                distance_matrix)

        print(u_pair)

        break

        variables = gn.variables
        grad = tape.gradient(loss, variables)
        optimizer.apply_gradients(
            zip(grad, variables))
```

```

```


# Dymos: The Basics

Dymos is a library for optimal control built on top of [OpenMDAO](https://github.com/OpenMDAO/OpenMDAO).
There are several quality optimal control packages available out there, both commercial and free, so why use Dymos?

1. OpenMDAO is a framework for multidisciplinary optimization.  It allows derivatives for gradient-based optimization to be calculated across complex models, even models which include iterative solvers.

2. Dymos makes use of gradient-based optimization of nonlinear transcriptions of optimal control problems: primarily Gauss-Lobatto collocation and the Radau Pseudospectral Method.  Using these techniques relies on the calculation of accurate derivatives across the model:  _If the initial state or parameter affecting a system is changed by x, how much does the final state change?_

# Installing Dymos

```
!pip install dymos
```

    Collecting dymos
      Downloading https://files.pythonhosted.org/packages/fb/7c/e022b98f411443cec74a94cdc51f4e2de61f5a4c8e733941a218d62ad3d5/dymos-0.17.0.tar.gz (317kB)
         |████████████████████████████████| 327kB 9.0MB/s
    Collecting openmdao>=3.3.0
      Downloading https://files.pythonhosted.org/packages/bc/ca/ebbcda6c8d7868eaf415004b8e53ef22fb10de7b9a6384b8b2a7834571eb/openmdao-3.5.0.tar.gz (4.6MB)
         |████████████████████████████████| 4.6MB 14.7MB/s
    Requirement already satisfied: numpy>=1.14.1 in /usr/local/lib/python3.6/dist-packages (from dymos) (1.18.5)
    Requirement already satisfied: scipy>=1.0.0 in /usr/local/lib/python3.6/dist-packages (from dymos) (1.4.1)
    Requirement already satisfied: networkx>=2.0 in /usr/local/lib/python3.6/dist-packages (from openmdao>=3.3.0->dymos) (2.5)
    Collecting pyDOE2
      Downloading https://files.pythonhosted.org/packages/70/1f/8a2e1b57f6dd22cd2002e4332e229e87a3858d560c516b50ab7fe5bb075c/pyDOE2-1.3.0.tar.gz
    Requirement already satisfied: pyparsing in /usr/local/lib/python3.6/dist-packages (from openmdao>=3.3.0->dymos) (2.4.7)
    Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from openmdao>=3.3.0->dymos) (2.23.0)
    Requirement already satisfied: decorator>=4.3.0 in /usr/local/lib/python3.6/dist-packages (from networkx>=2.0->openmdao>=3.3.0->dymos) (4.4.2)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.6/dist-packages (from requests->openmdao>=3.3.0->dymos) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.6/dist-packages (from requests->openmdao>=3.3.0->dymos) (2020.12.5)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.6/dist-packages (from requests->openmdao>=3.3.0->dymos) (2.10)
    Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.6/dist-packages (from requests->openmdao>=3.3.0->dymos) (1.24.3)
    Building wheels for collected packages: dymos, openmdao, pyDOE2
      Building wheel for dymos (setup.py) ... done
      Created wheel for dymos: filename=dymos-0.17.0-cp36-none-any.whl size=510569 sha256=4e20c1a656acc5bd08eb48a084f26444d4ccd0226d68ad3eb61057e283d3f730
      Stored in directory: /root/.cache/pip/wheels/80/0f/4e/c0a1ca83efbf8065746d13d3bde0d01685d91cbc1120aca992
      Building wheel for openmdao (setup.py) ... done
      Created wheel for openmdao: filename=openmdao-3.5.0-cp36-none-any.whl size=4848170 sha256=ab8fc16580f8a61f3615c4632aac6b50be9267387c5d5233cffa93c30aa6c827
      Stored in directory: /root/.cache/pip/wheels/c4/10/92/db967434445747069a88fa3c4165ddd5c101a74ecdee6d252f
      Building wheel for pyDOE2 (setup.py) ... done
      Created wheel for pyDOE2: filename=pyDOE2-1.3.0-cp36-none-any.whl size=25519 sha256=b9e83bb63a869155d76d90643c1f2c70126ef8a0f29a55c797c082299b4263c7
      Stored in directory: /root/.cache/pip/wheels/60/88/1d/b334ee00dd83e82d111ec56c3be91573d335c93870698037f1
    Successfully built dymos openmdao pyDOE2
    Installing collected packages: pyDOE2, openmdao, dymos
    Successfully installed dymos-0.17.0 openmdao-3.5.0 pyDOE2-1.3.0

# Simulating the flight of a basic projectile

Dymos can simulate the dynamics of systems which are defined by systems of **ordinary differential equations** or (in some cases) **differential algebraic equations**.

As a "Hello, World!" for Dymos, consider the differential equations which govern the flight of a projectile in a 2D, rectilinear, constant gravity field:

\begin{align}
  \ddot{x} &= 0 \\
  \ddot{y} &= -g
\end{align}

Here $x$ is the horizontal position of the projectile, $y$ is the height, and $g$ is the acceleration due to gravity.

To simulate this system in Dymos, we first convert this system of 2nd-order differential equations into 1st-order differential equations:

\begin{align}
  \dot{x} &= v_x \\
  \dot{y} &= v_y \\
  \dot{v_x} &= 0 \\
  \dot{v_y} &= -g
\end{align}

# The ODE as an OpenMDAO System

Dymos expects the ODE to be provided as an OpenMDAO _System_ - either a single Component, or a Group of components for more complex systems.

To define our system, we'll first need to import some packages.
Since we're not using parallel processing for this demo, we can ignore the warnings about `mpi4py` and `petsc4py`.

```
import numpy as np
import openmdao.api as om
import dymos as dm
```

    Unable to import mpi4py. Parallel processing unavailable.
    Unable to import petsc4py. Parallel processing unavailable.
    Unable to import petsc4py. Parallel processing unavailable.

The ODE System itself in this case is an OpenMDAO ExplicitComponent.  In most cases, ExplicitComponents used as ODE's in Dymos will need the following three methods defined:

## `initialize`

This method is used to declare options for Components and Groups in OpenMDAO.  In OpenMDAO, any option we create may be used as an initialization argument when we instantiate the class.  Dymos will always pass an option `num_nodes` to the ODE class.  This is the number of points at which the ODE is simultaneously evaluated.  This option is mandatory (it has no default value).

## `setup`

This is where inputs and outputs are added to our system.  In our case, the ODE accepts two inputs ('vx' and 'vy') and computes the time derivatives of our states: `x`, `y`, `vx`, and `vy`.

For this system, the derivatives of the states `x` and `y` are provided by the states `vx` and `vy`.  Therefore it isn't necessary to have our ODE compute their rates, but in this case we will do so for simplicity.

We use the OpenMDAO notion of `tags` to let Dymos know that a given output should be used to provide the rate of a state variable.  This makes the ODE a bit more modular, and prevents us from having to explicitly tell Dymos where to find the state's rate every time we use this ODE.  The two relevant tags here are:

- state_rate_source:foo

Provides the state rate source for the state named `foo`.

- state_units:str

Specify the default units for the associated state.

Finally, we use the `declare_partials` method here to tell OpenMDAO that we want to approximate the partials of each output with respect to each input as a dense matrix using finite-difference differentiation.  Later, we'll demonstrate how to improve the efficiency of Dymos by being smarter about how we compute our derivatives.

## `compute`

The compute method is where we take values from our inputs then compute and populate the outputs.  The `inputs` and `outputs` are both provided as a dictionary-like interface.

```
class ProjectileODE(om.ExplicitComponent):

  def initialize(self):
    self.options.declare('num_nodes', types=int,
                         desc='the number of points at which this ODE is simultaneously evaluated')

  def setup(self):
    nn = self.options['num_nodes']
    self.add_input('vx', shape=(nn,), units='m/s')
    self.add_input('vy', shape=(nn,), units='m/s')

    self.add_output('x_dot', shape=(nn,), units='m/s',
                    tags=['state_rate_source:x', 'state_units:m'])

    self.add_output('y_dot', shape=(nn,), units='m/s',
                    tags=['state_rate_source:y', 'state_units:m'])

    self.add_output('vx_dot', shape=(nn,), units='m/s**2',
                    tags=['state_rate_source:vx', 'state_units:m/s'])

    self.add_output('vy_dot', shape=(nn,), units='m/s**2',
                    tags=['state_rate_source:vy', 'state_units:m/s'])

    self.declare_partials(of='*', wrt='*', method='fd')

  def compute(self, inputs, outputs):
    outputs['x_dot'][:] = inputs['vx']
    outputs['y_dot'][:] = inputs['vy']
    outputs['vx_dot'][:] = 0.0
    outputs['vy_dot'][:] = -9.81

```

# Using the ODE

Let's simulate the trajectory of a cannonball (assuming no drag) using our ODE.  There are a few ways to do this in Dymos:

1. Explicitly - We can let `scipy.integrate.solve_ivp` propagate the ODE from some initial state.

2. Implicitly - We can guess what the trajectory of the cannonball looks like over some set of discrete points in time and then use a nonlinear solver or an optimizer to iterate on that initial guess until it matches the dynamics given by the ODE.

When solving large optimal control problems, Dymos will use the implicit approach to find control profile which minimizes some objective.

The explicit simulation is typically used to generate an initial guess or to check the accuracy of the implicit solution.

# Phases

The fundamental building block of trajectories in Dymos is the _Phase_.  A phase represents some portion of the trajectory in time.  We can break complex trajectories into many phases and then link them together to provide continuity in the states, but in a simple system we can often use a single phase to represent the entire trajectory.

# Transcriptions

Phases can be seen as a data structure which combines our ODE with some _transcription_.  In Dymos, a _transcription_ is a way of converting the _continous_ problem (finding polynomial representations of the optimal state and control histories of a system) into a _discrete_ representation that can be solved by a nonlinear solver or an nonlinear programming optimization algorithm.

The transcriptions in Dymos break a Phase into one or more segments.  On each segment, each state and each control is represented as a polynomial in non-dimensional time.  Systems in which the states are highly nonlinear, with lots of oscillations, generally require a lot of segments.  Our cannonball system here is pretty benign, and can be accurately modeled by a single cubic polynomial segment (the lowest order possible in Dymos).

# Creating the OpenMDAO Problem

So without further ado, let's instantiate an OpenMDAO problem and model our system as a Trajectory with a single Phase.

```
prob = om.Problem()

traj = prob.model.add_subsystem('traj', dm.Trajectory())

phase = traj.add_phase('phase', dm.Phase(ode_class=ProjectileODE, transcription=dm.Radau(num_segments=1, order=3)))
```

# Propagating the ODE from some initial condition for some set amount of time

In Dymos, each Phase has some _special_ variables: time, states, controls, and parameters.  To propagate our ODE, we'll need to apply some rules to our time and states - this problem has no control variables or parameters.

First, we'll tell Dymos that the initial time for the Phase should not be treated as a design variable for the optimization.  Whatever initial time we specify will remain unchanged throughout the optimization.

The duration of the phase is, in this case, limited to positive values from 1 second to 1000 seconds.  When optimizing the system, these _bounds_ prevent the optimizer from trying nonsensical values (negative times or a duratio of zero seconds.

```
phase.set_time_options(fix_initial=True, duration_bounds=(1, 1000), units='s')
```

Similarly, we're going to propagate the system from some known initial state, so we'll specify that that value is fixed.

```
phase.set_state_options('x', fix_initial=True)
phase.set_state_options('y', fix_initial=True)
phase.set_state_options('vx', fix_initial=True)
phase.set_state_options('vy', fix_initial=True)
```

Next we call OpenMDAO's `Problem.setup` method.  This works out the various connections between the different systems that Dymos uses to solve this problem and allocates memory.  You can think of this step a bit like compiling our model before we run it.

```
prob.setup()
```

    <openmdao.core.problem.Problem at 0x7f05ced7b358>

Now that the problem has been setup, we can specify guesses for the variables in our problem.

`<path_to_phase>.t_initial` - The initial time of the given phase.
`<path_to_phase>.t_duration` - The time duration of the given phase.

```
prob.set_val('traj.phase.t_initial', 0.0, units='s')
prob.set_val('traj.phase.t_duration', 15.0, units='s')
```

We provide guesses for the states across the phase.  Since it's difficult to know where the nodes of the transcription fall in time, Dymos provides some useful interpolation methods that can be used to provide guesses to the states in time.

Since `fix_initial` is `True`, the given value at the first node will not be changed throughout an optimization.

```
prob.set_val('traj.phase.states:x', phase.interpolate(ys=[0, 100], nodes='state_input'), units='m')
prob.set_val('traj.phase.states:y', phase.interpolate(ys=[0, 0], nodes='state_input'), units='m')
prob.set_val('traj.phase.states:vx', phase.interpolate(ys=[100, 100], nodes='state_input'), units='m/s')
prob.set_val('traj.phase.states:vy', phase.interpolate(ys=[100, -100], nodes='state_input'), units='m/s')
```

# Propagating the Trajectory

We'll use Dymos' convenience method, `run_problem` to execute the model without optimization (`run_driver=False`) and then simulate the problem from the given initial states for the given duration (`simulate=True`).

Results of the optimization are automatically saved to the OpenMDAO record file `dymos_solution.db`, while the simulation is saved to `dymos_simulation.db`.  Plots of all of our timeseries variables (states and state rates in this case) are made and saved in the `plots` subdirectory.

```
dm.run_problem(prob, run_driver=False, simulate=True, make_plots=True)
```

    Simulating trajectory traj
    Done simulating trajectory traj

```
# TODO: Make these more automatic (and use an interactive plot library like bokeh)

from IPython.display import display
from ipywidgets import widgets, GridBox

items = [widgets.Image(value=open(f'plots/states_{state}.png', 'rb').read()) for state in ['x', 'y', 'vx', 'vy']] + \
[widgets.Image(value=open(f'plots/state_rates_{state}.png', 'rb').read()) for state in ['x', 'y', 'vx', 'vy']]
widgets.GridBox(items, layout=widgets.Layout(grid_template_columns="repeat(2, 500px)"))

```

    GridBox(children=(Image(value=b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\xb0\x00\x00\x01 \x08\x06\x00\x…

The plots above show the _implicit_ solution of the states over time (as discrete points) in comparison to a more continuous integration of the states generated by `scipy.integrate.solve_ivp`.

There is disagreement between these two solutions.  Since no iteration was performed, the discrete points represent the interpolated initial guess we specified above, while the simulation results are more accurate (to the precision provided by the methods underlying `scipy.integrate.solve_ivp`.)

# Summary

The simulate method in Dymos is useful to see how the dynamics evolve, but does not currently provide a useful way of targeting or optimizing a trajectory.

For that, we need to use a nonlinear solver or optimizer to satisfy constraints in the system.

# Boundary Value Problem:  Vary duration such that the cannonball hits the ground

In order to solve a boundary value problem, we have to allow the system to iterate.  With OpenMDAO, that gives us two options:

1. Use an optimization driver to enforce the constraints.
2. Use a nonlinear solver to enforce the constraints.