├── .gitignore
├── LICENSE.txt
├── Makefile
├── README.rst
├── _templates
└── page.html
├── abstract.rst
├── acknowledgements.rst
├── conf.py
├── control.rst
├── davisbicycle.rst
├── delftbicycle.rst
├── eom.rst
├── extensions.rst
├── figure_table_numbers.py
├── foreword.rst
├── index-latex.rst
├── index.rst
├── introduction.rst
├── motioncapture.rst
├── parameterstudy.rst
├── physicalparameters.rst
├── preface.rst
├── sphinxmanual_mod.py
├── src
├── LICENSE.txt
├── control
│ ├── choose_gains.m
│ ├── control.m
│ ├── plot_gains.py
│ ├── publication_historgram.py
│ └── zero_wrt_speed.py
├── davisbicycle
│ ├── SteerTorqueEquations.py
│ ├── calibraton_fits.py
│ ├── fork_load.py
│ ├── lower_bearing_friction.m
│ ├── lower_ode.m
│ ├── perturbation_plot.py
│ ├── plot_processed.py
│ ├── roll_angle_trailer.py
│ ├── spring_ode.m
│ ├── spring_stiffness.m
│ ├── steer_dynamics_iddata.m
│ ├── steer_torque_components.py
│ ├── time_sync.py
│ ├── torque-wrench-data.txt
│ ├── torque-wrench.R
│ ├── total_bearing_friction.m
│ ├── total_ode.m
│ ├── upper_bearing_friction.m
│ └── upper_ode.m
├── defaults.cfg
├── eom
│ ├── Whipple.al
│ ├── WhippleDynamics.c
│ ├── WhippleDynamics.in
│ ├── basu_comparison.py
│ ├── crescendo_eigenvectors.py
│ ├── linear_comparison.py
│ ├── nonlinear_simulation.py
│ ├── plot_eigenvectors.py
│ └── whipple_pydy.py
├── extensions
│ ├── arms
│ │ ├── Arms.al
│ │ ├── ArmsDynamics.c
│ │ ├── ArmsDynamics.in
│ │ ├── arm_init.py
│ │ ├── arms.py
│ │ ├── arms_holonomic.m
│ │ ├── arms_integrate.m
│ │ ├── arms_linear.m
│ │ ├── arms_ode.m
│ │ ├── arms_points.m
│ │ ├── arms_reduce_system.m
│ │ ├── arms_state_space.m
│ │ ├── left_arm_constraint.m
│ │ ├── pitch_constraint.m
│ │ ├── plot_arms.m
│ │ └── right_arm_constraint.m
│ ├── gyro
│ │ ├── GyroBike.al
│ │ ├── GyroBikeDynamics.c
│ │ ├── GyroBikeDynamics.in
│ │ ├── gyrobike.py
│ │ └── gyrobike_linear.py
│ ├── hips.py
│ ├── lateral
│ │ ├── LateralForce.al
│ │ ├── autolev_linear.py
│ │ └── lateral_force.m
│ └── lean
│ │ ├── RiderLean.al
│ │ ├── RiderLeanDynamics.c
│ │ ├── RiderLeanDynamics.in
│ │ └── riderlean.py
├── load_paths.py
├── misc
│ └── convert_images.py
├── motioncapture
│ └── coordinates.py
├── parameterstudy
│ ├── bicycle_bode_compare.py
│ └── bicycle_eig_compare.py
├── physicalparameters
│ ├── bicycle_collage.py
│ └── par_tables.py
└── systemidentification
│ ├── canonical_fit_quality.py
│ ├── canonical_fit_quality_results.py
│ ├── canonical_plots.py
│ ├── canonical_table.py
│ ├── coefficient_box_plot.py
│ ├── coefficient_plot.py
│ ├── compare_id_freq.py
│ ├── control_parameters_vs_speed_plots.py
│ ├── crossover_plots.py
│ ├── data_histograms.py
│ ├── fit_plots.m
│ ├── global_minima.m
│ ├── load_rider_id_results.py
│ ├── output_fit_quality.py
│ ├── rider_id_model_quality.m
│ ├── rider_id_model_quality_plot.py
│ ├── run_time_history.py
│ ├── state_space_bode.py
│ └── tight_subplot.m
├── systemidentification.rst
├── todo.rst
├── word_count.py
└── zreferences.rst
/.gitignore:
--------------------------------------------------------------------------------
1 | # these are folders that I don't want deleted when commit to the gh-pages
2 | # branch
3 | figures-hide/
4 | tables-hide/
5 | data-hide/
6 |
7 | # Python
8 | *.pyc
9 |
10 | # Autolev
11 | *.all
12 | *.dir
13 | alTmp*
14 |
15 | # Matlab
16 | *.asv
17 | *.mat
18 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Text, Figures, Multimedia, and Data
2 | -----------------------------------
3 |
4 | Copyright (c) 2012, Jason Keith Moore
5 | All rights reserved.
6 |
7 | Creative Commons Attribution 3.0 Unported License
8 |
9 | http://creativecommons.org/licenses/by/3.0/
10 |
11 | Source Code
12 | -----------
13 |
14 | Copyright (c) 2012, Jason Keith Moore
15 | All rights reserved.
16 |
17 | Redistribution and use in source and binary forms, with or without
18 | modification, are permitted provided that the following conditions are met:
19 |
20 | 1. Redistributions of source code must retain the above copyright notice, this
21 | list of conditions and the following disclaimer.
22 |
23 | 2. Redistributions in binary form must reproduce the above copyright notice,
24 | this list of conditions and the following disclaimer in the documentation
25 | and/or other materials provided with the distribution.
26 |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
28 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 |
38 | The views and conclusions contained in the software and documentation are those
39 | of the authors and should not be interpreted as representing official policies,
40 | either expressed or implied, of Jason Keith Moore.
41 |
--------------------------------------------------------------------------------
/_templates/page.html:
--------------------------------------------------------------------------------
1 | {% extends "!page.html" %}
2 |
3 | {% block extrahead %}
4 | {{ super() }}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
27 | {% endblock %}
28 |
29 | {% block body %}
30 | {{ super() }}
31 |
36 |
Comments
37 |
38 |
48 |
50 | blog comments powered by
51 |
52 | {% endblock %}
53 | {% block footer %}
54 | {{ super() }}
55 |
63 |
64 |
75 |
76 | {% endblock %}
77 |
--------------------------------------------------------------------------------
/abstract.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Abstract
3 | ========
4 |
5 | *For the non-nonsensical it may be best to skip to the last section...*
6 |
7 | Unlike the plethora of scientific opuses that reveal how the average *Ursidae*
8 | (bear) can balance on a bicycle, little has been garnered about how *Homo
9 | sapiens* is able to accomplish this feat. When the rider's normal locomotion
10 | instruments for continual balance are replaced by two in-line wheels connected
11 | to one another by a manipulatable semi-vertical revolute joint the rider is
12 | then forced to direct his mental energy to observing the additional states of
13 | the bicycle's configuration and the proper actuation of his arms to maintain
14 | vertical equilibrium. It was found that this "simpler" task is well suited for
15 | manual control theoretic dissection and postulation.
16 |
17 | I herein present the findings of the seventh age of my tenure as a
18 | doctoral candidate in the prefecture of Mechanical and Aerospace Engineering at
19 | the Davis campus of the University of California. These describe our
20 | experimental procedure which involved strapping the untamed and aggressive
21 | *Homo sapiens* to a velocipede of extraordinary measurement capabilities. We
22 | perturbed the beasts as they tried with all their mental and physical might to
23 | stay upright, constrained as they were. Following more than seven hundred
24 | trials with three hand-picked quality specimens, the clouds of data have shaped
25 | into more than distant blurs. The control and identification tools of Bode,
26 | Evans, and Ljung combined with the modern day data management tools of van
27 | Rossum, Moler, and Torvalds have shed light on the details of the sensory
28 | feedback mechanisms present in the neurological pathways connecting the *Homo
29 | sapien*'s senses to his actuators. To the bear's dismay, this has in turn
30 | revealed that the highly regarded 1899 bicycle model of Whipple is sorely
31 | lacking and that the control theoretic hypothesis of McRuer for the great
32 | aeroplane pilots of yesteryear does, in fact, apply to the human control of a
33 | machine as simply complex as the bicycle.
34 |
35 | The dissertation will provide the reader with a glimpse into the reductio
36 | complexio of the physiological system of the greater *Homo sapiens* by forced
37 | travel on the automatic velocipede with highlights of manual control theories,
38 | inertial investigations, data wrangling, and of course demonstrations of the
39 | magical-like auto stability of the bicycle.
40 |
41 | *The sensical...*
42 |
43 | For those of you who'd rather read a more traditional abstract here is a quick
44 | explanation of the above in plain modern Engineering English:
45 |
46 | The bicycle, a simple toy to many, turns out to be an excellent platform to
47 | study the intricacies of vehicle dynamics, human-machine interaction, and human
48 | operator control for both academic and economical reasons. The bicycle is
49 | inherently unstable at low speeds and the human generally actuates the
50 | non-minimum phase system, and thus balances, only by means of rotating the
51 | handlebars. This dissertation describes a multi-year multi-person effort to
52 | better understand the dynamics of the bicycle, the biomechanics of the rider,
53 | and the rider's internal control system through theory and extensive
54 | experimentation.
55 |
56 | The chapters herein focus on the development of open loop bicycle models, some
57 | of which include the rider's biomechanics, the resulting predicted motion and
58 | model characteristics, the accurate measurement and estimation of both the
59 | bicycle and rider's physical parameters, observation of a rider's control
60 | motions, the development of experimental bicycles capable of measuring
61 | kinematics and forces, control theory including that of the human operator, and
62 | finally the identification of the both the plant and controller of the
63 | bicycle-rider system.
64 |
65 | The work has revealed a number of interesting conclusions including the primary
66 | biomechanic actuators used by the human in control, effects of the rider's
67 | motion and constraints on bicycle stability, the inadequacy of the Whipple
68 | bicycle model, and the ability of a simple multi-output control system based on
69 | the classical crossover model to describe the human's control efforts while
70 | being externally perturbed. These findings have implications in both single
71 | track vehicle design and human-machine interaction theory. Future applications
72 | may be able to utilize the methods and results to help objectively design
73 | bicycles with improved handling, stability, and controllability whereas human
74 | operator research may be able to build on the validated crossover model theory
75 | of manual control.
76 |
--------------------------------------------------------------------------------
/figure_table_numbers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import re
4 |
5 | chapters = ['abstract',
6 | 'foreword',
7 | 'acknowledgements',
8 | 'preface',
9 | 'introduction',
10 | 'eom',
11 | 'extensions',
12 | 'physicalparameters',
13 | 'parameterstudy',
14 | 'delftbicycle',
15 | 'motioncapture',
16 | 'davisbicycle',
17 | 'control',
18 | 'systemidentification']
19 |
20 | def old2new(matchobj):
21 | oldNum, figNum, typ = matchobj.groups()
22 | newNum = str(int(oldNum) - 2)
23 | return '{}.{}<{}'.format(newNum, figNum, typ)
24 |
25 | def new2old(matchobj):
26 | newNum, figNum, typ = matchobj.groups()
27 | oldNum = str(int(newNum) + 2)
28 | return '{}.{}<{}'.format(oldNum, figNum, typ)
29 |
30 | funcs = {'new2old': new2old, 'old2new': old2new}
31 |
32 | def convert(to):
33 | for chapter in chapters:
34 | with open(chapter + '.rst') as f:
35 | text = f.read()
36 | newText = re.sub(r'(\d*)\.(\d*) *<(fig|tab)', funcs[to], text)
37 | with open(chapter + '.rst', 'w') as f:
38 | f.write(newText)
39 |
--------------------------------------------------------------------------------
/foreword.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Foreword
3 | ========
4 |
5 | On rare occasion in life one must halt everything in order to focus on one
6 | monumental task of great importance. Few really comprehend the inordinate
7 | amount of time, effort and sacrifice that goes into a dissertation. This paper
8 | was such an endeavor.
9 |
10 | Around the world millions of people ride their bikes daily, yet the secrets
11 | that make the magic of bicycling possible have never been fully understood or
12 | entirely quantified. This work brings us closer to that goal. The melding of
13 | man and machine that allows for transportation and gratification has always
14 | been somewhat of a mystery. This compilation delves into the foundations of
15 | bicycling and investigates why it’s possible.
16 |
17 | These principles, when fully understood, will greatly enhance our understanding
18 | and perhaps change the world as we know it... at least the bicycling world.
19 |
20 | | Jan Phillip Wright
21 | | 8. 13. 2012
22 | | University of California, Davis
23 |
--------------------------------------------------------------------------------
/index-latex.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Contents
3 | ========
4 |
5 | .. toctree::
6 | :maxdepth: 2
7 |
8 | preface
9 | introduction
10 | eom
11 | extensions
12 | physicalparameters
13 | parameterstudy
14 | delftbicycle
15 | motioncapture
16 | davisbicycle
17 | control
18 | systemidentification
19 |
20 | .. bibliography:: data/bicycle.bib
21 | :encoding: latex+utf8
22 | :style: alpha
23 |
--------------------------------------------------------------------------------
/index.rst:
--------------------------------------------------------------------------------
1 | ========
2 | Contents
3 | ========
4 |
5 | This is the University of California, Davis Mechanical and Aerospace
6 | Engineering doctoral dissertation of Jason K. Moore.
7 |
8 | .. image:: figures/bear-6in.*
9 | :align: center
10 | :width: 600px
11 | :target: _images/bear-6in.png
12 |
13 | .. toctree::
14 | :maxdepth: 2
15 |
16 | abstract
17 | foreword
18 | acknowledgements
19 | preface
20 | introduction
21 | eom
22 | extensions
23 | physicalparameters
24 | parameterstudy
25 | delftbicycle
26 | motioncapture
27 | davisbicycle
28 | control
29 | systemidentification
30 | zreferences
31 |
32 | * :ref:`search`
33 |
34 | .. note:: `Download the latest pdf version of this
35 | document `_.
36 |
--------------------------------------------------------------------------------
/sphinxmanual_mod.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """This file needs to be run in after Sphinx's tex generation but before the
4 | latex or pdflatex calls."""
5 |
6 | import re
7 |
8 | # Remove the numbering and page style stuff in the toc redefintion in the class
9 | # file. I'm setting these in the toc variable in conf.py.
10 | with open('_build/latex/sphinxmanual.cls', 'r') as f:
11 | text = f.read()
12 |
13 | pattern = r"\\setcounter{page}\{1\}%\n \\pagebreak%\n \\pagestyle{plain}%"
14 | replace = r"\pagebreak%"
15 | fixed = re.sub(pattern, replace, text)
16 | fixed = fixed.replace(r'\pagenumbering{arabic}', '')
17 | fixed = re.sub(r' {\\em\\LARGE\\py@HeaderFamily \\py@release\\releaseinfo \\par}\n', '', fixed)
18 | fixed = re.sub(r' \\rule{\\textwidth}{1pt}%\n', '', fixed)
19 |
20 | fixed = re.sub(r' \\let\\footnotesize\\small\n',
21 | r' \\setcounter{page}{3}\n \\let\\footnotesize\\small\n', fixed)
22 |
23 | # if i try to add frontmatter to the title page to get a page number on the
24 | # page but the the /marks seem to break in the document
25 | #\\thispagestyle{frontmatter}\n
26 | # or
27 | # \\pagestyle{frontmatter}
28 |
29 | fixed = fixed.replace(r'\@date', 'August 2012')
30 | fixed = fixed.replace(r'\vfill\vfill', r'\vfill')
31 |
32 | with open('_build/latex/sphinxmanual.cls', 'w') as f:
33 | f.write(fixed)
34 |
35 | # Add a new frontmatter header style to the sphinx style file.
36 | with open('_build/latex/sphinx.sty', 'r') as f:
37 | text = f.read()
38 |
39 | pattern = (r" \\renewcommand{\\headrulewidth}{0pt}\n" +
40 | r" \\renewcommand{\\footrulewidth}{0.4pt}\n }")
41 | replace = \
42 | r""" \\renewcommand{\\headrulewidth}{0pt}
43 | \\renewcommand{\\footrulewidth}{0.4pt}
44 | }
45 | \\fancypagestyle{frontmatter}{
46 | \\fancyhf{}
47 | \\fancyfoot[CE,CO]{{--\\py@HeaderFamily\\thepage--}}
48 | \\renewcommand{\\headrulewidth}{0pt}
49 | \\renewcommand{\\footrulewidth}{0pt}
50 | }"""
51 | fixed = re.sub(pattern, replace, text)
52 |
53 | fixed = re.sub(r', \\py@release', '', fixed)
54 |
55 | fixed = re.sub(r'\\py@HeaderFamily \\@title}}\n',
56 | r'\\py@HeaderFamily \\@title}}\n \\fancyhead[RE,LO]{{\\py@HeaderFamily \\@author}}\n',
57 | fixed)
58 |
59 | with open('_build/latex/sphinx.sty', 'w') as f:
60 | f.write(fixed)
61 |
62 | # Add the signature pages to the front of the document.
63 | with open('_build/latex/HumanControlofaBicycle.tex', 'r') as f:
64 | text = f.read()
65 |
66 | fixed = re.sub(r'\\maketitle',
67 | r'\\includepdf[pages={3,{}}]{../../data/ucd-pages.pdf}\n\\maketitle',
68 | text)
69 |
70 | with open('_build/latex/HumanControlofaBicycle.tex', 'w') as f:
71 | f.write(fixed)
72 |
--------------------------------------------------------------------------------
/src/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, Jason Keith Moore
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | The views and conclusions contained in the software and documentation are those
26 | of the authors and should not be interpreted as representing official policies,
27 | either expressed or implied, of Jason Keith Moore.
28 |
--------------------------------------------------------------------------------
/src/control/plot_gains.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This plots the change in gains computed by our bicycle Human control software
4 | # for a particular bicycle.
5 |
6 | import matplotlib.pyplot as plt
7 |
8 | def load_gains(fileName):
9 | """Loads the gains from the file into a dictionary."""
10 | f = open(fileName, 'r')
11 | header = f.readline()
12 | variables = header.strip().split(',')
13 | data = {k : [] for k in variables}
14 | for line in f:
15 | values = line.strip().split(',')
16 | for i, val in enumerate(values):
17 | data[variables[i]].append(val)
18 | return data
19 |
20 | data = load_gains('/media/Data/Documents/School/UC Davis/Bicycle Mechanics/HumanControl/gains/RigidJasonSteerGains.txt')
21 |
22 | fig_width = 4.0
23 | fig_height = 5.0
24 | fig_size = [fig_width, fig_height]
25 | params = {'axes.labelsize': 10,
26 | 'text.fontsize': 10,
27 | 'legend.fontsize': 10,
28 | 'xtick.labelsize': 8,
29 | 'ytick.labelsize': 8,
30 | 'text.usetex': True,
31 | 'figure.figsize': fig_size,
32 | 'figure.subplot.hspace': 0.15,
33 | 'figure.subplot.bottom': 0.1,
34 | 'figure.subplot.left': 0.2}
35 | plt.rcParams.update(params)
36 |
37 | fig = plt.figure()
38 |
39 | gains = ['kDelta', 'kPhiDot', 'kPhi', 'kPsi', 'kY']
40 | labels = ['$k_\delta$', '$k_\dot{\phi}$', '$k_\phi$', '$k_\psi$', '$k_{y_q}$']
41 | axes = []
42 |
43 | for i, (gain, label) in enumerate(zip(gains, labels)):
44 | ax = (fig.add_subplot(5, 1, i + 1))
45 | ax.plot(data['speed'], data[gain])
46 | ax.set_ylabel(label)
47 | axes.append(ax)
48 | ax.locator_params(axis='y', nbins=5)
49 |
50 | for ax in axes[0:-1]:
51 | ax.set_xticklabels('')
52 |
53 | axes[-1].set_xlabel('Speed [m/s]')
54 |
55 | fig.savefig('../../figures/control/gains.png', dpi=200)
56 | fig.savefig('../../figures/control/gains.pdf')
57 |
--------------------------------------------------------------------------------
/src/control/publication_historgram.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from pybtex.database.input import bibtex
4 | import matplotlib.pyplot as plt
5 |
6 | parser = bibtex.Parser()
7 | path = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/Papers/bicycle.bib'
8 | bib_data = parser.parse_file(path)
9 | years = []
10 | for entry in bib_data.entries:
11 | try:
12 | years.append(int(bib_data.entries[entry].fields['year']))
13 | except:
14 | pass
15 |
16 | plt.hist(years, bins=40)
17 | plt.title('{} Total Publications'.format(len(years)))
18 | plt.xticks(range(1860, 2030, 10), fontsize=6, rotation=20.0)
19 | plt.yticks(range(0, 140, 20), fontsize=6)
20 | plt.ylabel('Number of Publications')
21 | plt.xlabel('Year')
22 | fig = plt.gcf()
23 | fig.set_size_inches(4.0, 4.0)
24 | fig.savefig('../../figures/control/pub-hist.png', dpi=200)
25 | fig.savefig('../../figures/control/pub-hist.pdf')
26 | plt.show()
27 |
--------------------------------------------------------------------------------
/src/control/zero_wrt_speed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This script calculates the zeros of the steer torque to steer and roll angle
4 | # tranfer functions as a function of the canonical matrix entries given in
5 | # Meijaard2007.
6 |
7 | # Warning! This script does not work if there are complex zeros, as I haven't
8 | # figured out how to change the sympy `I` into a numpy complex number.
9 |
10 | import numpy as np
11 | import matplotlib.pyplot as plt
12 | from sympy import Symbol, Matrix, symbols, eye, zeros, roots
13 | import uncertainties as un
14 | import bicycleparameters as bp
15 |
16 | # v = speed, g = acceleration due to gravity, s = Laplace variable
17 | v, g, s = symbols('v g s')
18 |
19 | # build the canoncial matrices, setting some entries to zero
20 | # I did not include these realtionships yet: M[0, 1] = M[1, 0] and K0[0, 1] =
21 | # K0[1, 0]. It is possible that they help simplify things.
22 | # I did set C[0, 0] = 0 and K2[0, 0] = K2[1, 0] = 0.
23 | M = Matrix(2, 2, lambda i, j: Symbol('m' + str(i + 1) + str(j + 1)))
24 | C1 = Matrix(2, 2, lambda i, j: 0 if i == 0 and j == 0 else
25 | Symbol('c1' + str(i + 1) + str(j + 1)))
26 | K0 = Matrix(2, 2, lambda i, j: Symbol('k0' + str(i + 1) + str(j + 1)))
27 | K2 = Matrix(2, 2, lambda i, j: 0 if j == 0 else
28 | Symbol('k2' + str(i + 1) + str(j + 1)))
29 |
30 | # Build the A, B and C matrices such that steer torque is the only input and
31 | # phi and delta are the only outputs.
32 | Minv = M.inv()
33 |
34 | Abl = -Minv * (g * K0 + v**2 * K2)
35 | Abr = -Minv * v * C1
36 |
37 | A = zeros(2).row_join(eye(2)).col_join(Abl.row_join(Abr))
38 |
39 | #B = zeros((2, 1)).col_join(Minv[:, 1])
40 | B = zeros(2).col_join(Minv)
41 |
42 | C = Matrix([[1, 0, 0, 0], [0, 1, 0, 0]])
43 |
44 | # Calculate the transfer function numerator polynomial. See Hoagg2007 for the
45 | # equation.
46 | N = C * (s * eye(4) - A).adjugate() * B
47 | # These are the roll and steer numerator polynomials, respectively.
48 | phiTphi = N[0, 0]
49 | deltaTphi = N[1, 0]
50 | phiTdelta = N[0, 1]
51 | deltaTdelta = N[1, 1]
52 |
53 | # Find the zeros
54 | phiTphiZeros = roots(phiTphi, s, multiple=True)
55 | deltaTphiZeros = roots(deltaTphi, s, multiple=True)
56 | phiTdeltaZeros = roots(phiTdelta, s, multiple=True)
57 | deltaTdeltaZeros = roots(deltaTdelta, s, multiple=True)
58 |
59 | # Load a bicycle with some parameters and calculate the canonical matrices.
60 | pathToData = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data'
61 | bicycle = bp.Bicycle('Rigidcl', pathToData=pathToData, forceRawCalc=True)
62 | bicycle.add_rider('Charlie')
63 | #bicycle = bp.Bicycle('Benchmark', pathToData=pathToData)
64 | Mn, C1n, K0n, K2n = bicycle.canonical()
65 | Mn = un.unumpy.nominal_values(Mn)
66 | C1n = un.unumpy.nominal_values(C1n)
67 | K0n = un.unumpy.nominal_values(K0n)
68 | K2n = un.unumpy.nominal_values(K2n)
69 |
70 | # Create a dictionary to substitute numerical values.
71 |
72 | # These are the benchmark bicycle parameters.
73 | #num = {M[0, 0] : 80.81722,
74 | #M[0, 1] : 2.31941332208709,
75 | #M[1, 0] : 2.31941332208709,
76 | #M[1, 1] : 0.29784188199686,
77 | #K0[0, 0] : -80.95,
78 | #K0[0, 1] : -2.59951685249872,
79 | #K0[1, 0] : -2.59951685249872,
80 | #K0[1, 1] : -0.80329488458618,
81 | #K2[0, 1] : 76.59734589573222,
82 | #K2[1, 1] : 2.65431523794604,
83 | #C1[0, 1] : 33.86641391492494,
84 | #C1[1, 0] : -0.85035641456978,
85 | #C1[1, 1] : 1.68540397397560,
86 | #g : 9.81}
87 |
88 | num = {M[0, 0] : Mn[0, 0],
89 | M[0, 1] : Mn[0, 1],
90 | M[1, 0] : Mn[1, 0],
91 | M[1, 1] : Mn[1, 1],
92 | K0[0, 0] : K0n[0, 0],
93 | K0[0, 1] : K0n[0, 1],
94 | K0[1, 0] : K0n[1, 0],
95 | K0[1, 1] : K0n[1, 1],
96 | K2[0, 1] : K2n[0, 1],
97 | K2[1, 1] : K2n[1, 1],
98 | C1[0, 1] : C1n[0, 1],
99 | C1[1, 0] : C1n[1, 0],
100 | C1[1, 1] : C1n[1, 1],
101 | g : 9.81}
102 |
103 | # Now calculate the zeros as a function of speed and plot them.
104 | speeds = np.linspace(0, 10, num=50)
105 |
106 | phiTphiRoots = []
107 | deltaTphiRoots = []
108 | phiTdeltaRoots = []
109 | deltaTdeltaRoots = []
110 |
111 | for speed in speeds:
112 | # set the speed
113 | num[v] = speed
114 | phiTphiRoots.append([x.subs(num) for x in phiTphiZeros])
115 | deltaTphiRoots.append([x.subs(num) for x in deltaTphiZeros])
116 | phiTdeltaRoots.append([x.subs(num) for x in phiTdeltaZeros])
117 | deltaTdeltaRoots.append([x.subs(num) for x in deltaTdeltaZeros])
118 |
119 | goldenRatio = (5**0.5 - 1.0) / 2.0
120 | fig_width = 5.0
121 | fig_height = fig_width * goldenRatio
122 | fig_size = [fig_width, fig_height]
123 | params = {'axes.labelsize': 10,
124 | 'text.fontsize': 8,
125 | 'legend.fontsize': 10,
126 | 'xtick.labelsize': 6,
127 | 'ytick.labelsize': 6,
128 | 'figure.titlesize': 10,
129 | 'text.usetex': True,
130 | 'figure.figsize': fig_size,
131 | 'figure.subplot.hspace': 0.1,
132 | 'figure.subplot.bottom': 0.15,
133 | 'figure.subplot.left': 0.15}
134 | plt.rcParams.update(params)
135 |
136 | fig = plt.figure()
137 |
138 | ax1 = fig.add_subplot(4, 1, 1)
139 | ax1.plot(speeds, phiTphiRoots, '.b')
140 | ax1.set_ylabel(r'$\frac{\phi}{T_\phi}$')
141 | ax1.set_title('Transfer function zeros')
142 | ax1.set_xticklabels('')
143 | ax1.grid()
144 |
145 | ax2 = fig.add_subplot(4, 1, 2)
146 | ax2.plot(speeds, deltaTphiRoots, '.b')
147 | ax2.set_ylabel(r'$\frac{\delta}{T_\phi}$')
148 | ax2.set_xlabel('Speed [m/s]')
149 | ax2.set_xticklabels('')
150 | ax2.grid()
151 |
152 | ax3 = fig.add_subplot(4, 1, 3)
153 | ax3.plot(speeds, phiTdeltaRoots, '.b')
154 | ax3.set_ylabel(r'$\frac{\phi}{T_\delta}$')
155 | ax3.set_xticklabels('')
156 | ax3.grid()
157 |
158 | ax4 = fig.add_subplot(4, 1, 4)
159 | ax4.plot(speeds, deltaTdeltaRoots, '.b')
160 | ax4.set_ylabel(r'$\frac{\delta}{T_\delta}$')
161 | ax4.set_xlabel('Speed [m/s]')
162 | ax4.set_ylim((-3.5, 3.5))
163 | ax4.grid()
164 |
165 | for ax in [ax1, ax2, ax3, ax4]:
166 | ax.locator_params(axis='y', nbins=5)
167 |
168 | fig.savefig('../../figures/control/zeros-wrt-speed.png', dpi=200)
169 | fig.savefig('../../figures/control/zeros-wrt-speed.pdf')
170 |
--------------------------------------------------------------------------------
/src/davisbicycle/SteerTorqueEquations.py:
--------------------------------------------------------------------------------
1 | import sympy as sym
2 | import sympy.physics.mechanics as me
3 |
4 | """
5 | This script calculates the actual steer torque applied to the handlebars as a
6 | function of the measurements taken on the bicycle.
7 | """
8 |
9 | #--------------------#
10 | # measured constants #
11 | #--------------------#
12 |
13 | # d : distance from the handlebar center of mass to the point s on the steer
14 | # axis
15 | # ds1, ds3 : measure numbers for the location of the vectornav to the point on
16 | # the steer axis, s
17 | d, ds1, ds3 = sym.symbols('d ds1 ds3')
18 |
19 | # handlebar inertia and mass
20 | IG11, IG22, IG33, IG31 = sym.symbols('IG11 IG22 IG33 IG31')
21 | mG = sym.symbols('mG')
22 |
23 | #----------------------------------------------------------------#
24 | # time varying signals that are directly measured on the bicycle #
25 | #----------------------------------------------------------------#
26 |
27 | # measured steer column torque and upper bearing friction force
28 | Tm, Tu = me.dynamicsymbols('Tm Tu')
29 | # steer angle and body fixed handlebar rate about steer axis
30 | delta, wg3 = me.dynamicsymbols('delta wg3')
31 | # body fixed angular rates of the bicycle frame
32 | wb1, wb2, wb3 = me.dynamicsymbols('wb1 wb2 wb3')
33 | # body fixed acceleration of the vectornav point
34 | av1, av2, av3 = me.dynamicsymbols('av1 av2 av3')
35 |
36 | #-----------------------------------------------------------------------#
37 | # time varying signals that can be calculated from the measured signals #
38 | #-----------------------------------------------------------------------#
39 |
40 | # steer
41 | deltad, wg3d = me.dynamicsymbols('delta wg3', 1)
42 | # body fixed angular acceleration
43 | wb1d, wb2d, wb3d = me.dynamicsymbols('wb1 wb2 wb3', 1)
44 |
45 | #-----------------------------#
46 | # define the reference frames #
47 | #-----------------------------#
48 |
49 | # newtonian frame
50 | N = me.ReferenceFrame('N', indices=('1', '2', '3'))
51 | # bicycle frame
52 | B = me.ReferenceFrame('B', indices=('1', '2', '3'))
53 | # handlebar frame
54 | G = me.ReferenceFrame('G', indices=('1', '2', '3'))
55 | G.orient(B, 'Axis', (delta, B['3']))
56 |
57 | #------------------------------------#
58 | # define the locations of the points #
59 | #------------------------------------#
60 |
61 | # vectornav center
62 | v = me.Point('v')
63 |
64 | # point on the steer axis
65 | s = me.Point('s')
66 | s.set_pos(v, ds1 * B['1'] + ds3 * B['3'])
67 |
68 | # handlebar center of mass
69 | go = me.Point('go')
70 | go.set_pos(s, d * G['1'])
71 |
72 | #---------------------------------------------------#
73 | # set the known angular velocites and accelerations #
74 | #---------------------------------------------------#
75 |
76 | # set the angular velocities of B and G
77 | B.set_ang_vel(N, wb1 * B['1'] + wb2 * B['2'] + wb3 * B['3'])
78 | G.set_ang_vel(N, (wb1 * sym.cos(delta) + wb2 * sym.sin(delta)) * G['1'] +
79 | (-wb1 * sym.sin(delta) + wb2 * sym.cos(delta)) * G['2'] + wg3 * G['3'])
80 |
81 | # set the acceleration of point v
82 | v.set_acc(N, av1 * B['1'] + av2 * B['2'] + av3 * B['3'])
83 |
84 | #------------------------------------------------------------#
85 | # calculate the acceleration of the handlebar center of mass #
86 | #------------------------------------------------------------#
87 |
88 | s.a2pt_theory(v, N, B)
89 | go.a2pt_theory(s, N, G)
90 |
91 | #-------------------------------#
92 | # handlebar equations of motion #
93 | #-------------------------------#
94 |
95 | # calculate the angular momentum of the handlebar in N about the center of mass
96 | # of the handlebar
97 | IG = me.inertia(G, IG11, IG22, IG33, 0, 0, IG31)
98 | H_G_N_go = IG.dot(G.ang_vel_in(N))
99 |
100 | Hdot = H_G_N_go.dt(N)
101 |
102 | # euler's equation about an arbitrary point, s
103 | sumT = Hdot + go.pos_from(s).cross(mG * go.acc(N))
104 |
105 | # calculate the steer torque
106 | Tdelta = sumT.dot(G['3']) + Tm + Tu
107 |
108 | # let's make use of the steer rate gyro and frame rate gyro measurement instead
109 | # of differentiating delta
110 | Tdelta = Tdelta.subs(deltad, wg3 - wb3)
111 |
112 | print "Tdelta as a function of the measured data:\nTdelta =", Tdelta
113 |
--------------------------------------------------------------------------------
/src/davisbicycle/calibraton_fits.py:
--------------------------------------------------------------------------------
1 | from numpy import array, sqrt
2 | from scipy.optimize import curve_fit
3 | import matplotlib.pyplot as plt
4 | from dtk.process import fit_goodness
5 |
6 | # matplotlib settings
7 | golden_mean = (sqrt(5) - 1.0) / 2.0
8 | fig_width = 3.0
9 | fig_height = fig_width * golden_mean
10 | params = {'backend': 'ps',
11 | 'axes.labelsize': 8,
12 | 'ytick.labelsize': 6,
13 | 'axes.titlesize': 8,
14 | 'text.fontsize': 8,
15 | 'legend.fontsize': 6,
16 | 'xtick.labelsize': 6,
17 | 'text.usetex': True,
18 | 'figure.figsize': [fig_width,fig_height],
19 | 'figure.dpi' : 300,
20 | 'figure.title.fontsize': 8,
21 | }
22 | plt.rcParams.update(params)
23 |
24 | # The wheel speed motor regression.
25 | rpm = array([42.5, 62.0, 89.0, 132.0, 185.0, 271.5, 391.0, 569.0, 855.0, 1243.0,
26 | 1785.0, 2588.0])
27 |
28 | voltage = array([0.094, 0.1385, 0.199, 0.291, 0.406, 0.595, 0.857, 1.252, 1.879,
29 | 2.738, 3.91, 5.67])
30 |
31 | line = lambda x, m, b: m * x + b
32 |
33 | (m, b), pcov = curve_fit(line, voltage, rpm)
34 |
35 |
36 | rsq, SSE, SST, SSR = fit_goodness(rpm, line(voltage, m, b))
37 |
38 | fig, ax = plt.subplots()
39 |
40 | ax.scatter(voltage, rpm, label='Measured')
41 | ax.plot(voltage, line(voltage, m, b), 'g', label='Best Fit')
42 | ax.set_xlabel('Motor Voltage [V]')
43 | ax.set_ylabel('Rotational Speed [rpm]')
44 | ax.annotate(r'$r^2={:1.3f}$\\$m={:1.0f}$\\$b={:1.2f}$'.format(rsq, m, b), (0.0,
45 | 1000.0), fontsize=8)
46 | ax.legend(loc=4)
47 |
48 | fig.tight_layout()
49 |
50 | fig.savefig('../../figures/davisbicycle/speed-calibration.png', dpi=300)
51 | fig.savefig('../../figures/davisbicycle/speed-calibration.pdf')
52 |
53 | # The steer torque sensor regression.
54 |
55 | load = array([0, 30, 60, 90, 120, 150, 0, -0, -30, -60, -90, -120, -150, -0])
56 |
57 | voltage = array([0.000, 1.998, 3.993, 5.997, 7.994, 9.997, 0.002, 0.000,
58 | -1.995, -3.994, -5.989, -7.986, -9.986, 0.002])
59 |
60 | (m, b), pcov = curve_fit(line, voltage, load)
61 |
62 | rsq, SSE, SST, SSR = fit_goodness(load, line(voltage, m, b))
63 |
64 | fig, ax = plt.subplots()
65 |
66 | ax.scatter(voltage, load, label='Measured')
67 | ax.plot(voltage, line(voltage, m, b), 'g', label='Best Fit')
68 | ax.set_xlabel('Sensor Voltage [V]')
69 | ax.set_ylabel('Load [in-lb]')
70 | ax.annotate(r'$r^2={:1.3f}$\\$m={:1.0f}$\\$b={:1.2f}$'.format(rsq, m, b),
71 | (-10.0, 0.0), fontsize=8)
72 | ax.legend(loc=4)
73 |
74 | fig.tight_layout()
75 |
76 | fig.savefig('../../figures/davisbicycle/torque-calibration.png', dpi=300)
77 | fig.savefig('../../figures/davisbicycle/torque-calibration.pdf')
78 |
--------------------------------------------------------------------------------
/src/davisbicycle/lower_bearing_friction.m:
--------------------------------------------------------------------------------
1 | function [cl, tl, ISF] = lower_bearing_friction(varargin)
2 | % function [cl, tl, ISF] = lower_bearing_friction(varargin)
3 | %
4 | % Returns estimated parameters for the lower bearing friction in the steer
5 | % column.
6 | %
7 | % Parameters
8 | % ----------
9 | % varargin : cell array of booleans, 1 x 3, optional
10 | % Specify true or false for each parameter. True will fix the parameter
11 | % and false will allow it to be estimated. Parameters are cl, tl, and ISF.
12 | %
13 | % Returns
14 | % -------
15 | % cl : double, 7 x 1
16 | % The viscous friction coefficient in N*m*s.
17 | % tl : double, 7 x 1
18 | % The Coulomb friction torque in N*m.
19 | % ISF : double, 7 x 1
20 | % The moment of inertia of the fork and front wheel (everything below the
21 | % torque sensor) about the steer axis in kg*m^2.
22 |
23 | % set the initial guesses and bounds on the parameters
24 | par(1, 1).Name = 'cl';
25 | par(1, 1).Unit = '';
26 | par(1, 1).Value = 0.2;
27 | par(1, 1).Minimum = 0;
28 | par(1, 1).Maximum = Inf;
29 | par(1, 1).Fixed = 0;
30 |
31 | par(2, 1).Name = 'tl';
32 | par(2, 1).Unit = '';
33 | par(2, 1).Value = 0.0349;
34 | par(2, 1).Minimum = 0;
35 | par(2, 1).Maximum = Inf;
36 | par(2, 1).Fixed = 0;
37 |
38 | % fork and front wheel moment of inertia about steer axis
39 | % the inertia is from the bicycle parameters measurements
40 | par(3, 1).Name = 'ISF';
41 | par(3, 1).Unit = 'kg*m^2';
42 | par(3, 1).Value = 0.06405009;
43 | par(3, 1).Minimum = 0;
44 | par(3, 1).Maximum = Inf;
45 | par(3, 1).Fixed = 1;
46 |
47 | % set the initial conditions
48 | init(1, 1).Name = 'deltal';
49 | init(1, 1).Value = 0;
50 | init(1, 1).Unit = '';
51 | init(1, 1).Minimum = -Inf;
52 | init(1, 1).Maximum = Inf;
53 | init(1, 1).Fixed = 0;
54 |
55 | init(2, 1).Name = 'omegal';
56 | init(2, 1).Value = 0;
57 | init(2, 1).Unit = '';
58 | init(2, 1).Minimum = -Inf;
59 | init(2, 1).Maximum = Inf;
60 | init(2, 1).Fixed = 0;
61 |
62 | % build the nonlinear grey box model
63 | order = [1, 1, 2]; % [outputs, inputs, states]
64 | grey = idnlgrey('lower_ode', order, par, init, 0);
65 | set(grey, 'InputName', 'SteerTubeTorque')
66 | set(grey, 'OutputName', {'SteerAngle'})
67 |
68 | % runs 205-216 have the rate gyro mounted in the normal position (i.e. steer
69 | % angle and steer rate are measured at the same place)
70 | %runs = 211:216;
71 | runs = 216;
72 |
73 | cl = zeros(length(runs), 1);
74 | tl = zeros(length(runs), 1);
75 | ISF = zeros(length(runs), 1);
76 |
77 | for i = 1:length(runs)
78 | [data, delta0, omega0] = steer_dynamics_iddata(['00' num2str(runs(i)) ...
79 | '.mat'], {'SteerAngle'}, true);
80 |
81 | % if fixed is supplied, apply it
82 | if size(varargin, 2) > 0
83 | if size(varargin{1}, 2) ~= 3
84 | error('You must supply three booleans.')
85 | else
86 | setpar(grey, 'Fixed', varargin{1})
87 | end
88 | end
89 |
90 | % update the initial condition guesses
91 | setinit(grey, 'Value', {delta0, omega0})
92 |
93 | % find the best fit
94 | fit = pem(data, grey);
95 |
96 | % store the results
97 | cl(i) = fit.Par(1).Value;
98 | tl(i) = fit.Par(2).Value;
99 | ISF(i) = fit.Par(3).Value;
100 |
101 | % show the plots
102 | figure(i)
103 | compare(data, grey, fit);
104 | end
105 |
--------------------------------------------------------------------------------
/src/davisbicycle/lower_ode.m:
--------------------------------------------------------------------------------
1 | function [dx, y] = lower_ode(t, x, u, cl, tl, ISF, varargin)
2 | % function [dx, y] = lower_ode(t, x, u, cl, tl, ISF, varargin)
3 | %
4 | % Returns the state derivatives and the outputs of the fork and front wheel
5 | % (below torque sensor) motion.
6 | %
7 | % Parameters
8 | % ----------
9 | % t : double
10 | % Current time [s].
11 | % x : double, 2 x 1
12 | % Current state [deltal; omegal].
13 | % u : double
14 | % Current input, Tm [Nm].
15 | % cl : double
16 | % Viscous friction coefficient [N*m*s].
17 | % tl : double
18 | % Coulomb friction coefficient [N*m].
19 | % ISF : double
20 | % Fork and front wheel moment of inertia about the steer axis [kg*m^2].
21 | % varargin : cell array
22 | % Optional arguments passed through the idnlgrey/pem calls.
23 | %
24 | % Returns
25 | % -------
26 | % dx : double, 2 x 1
27 | % The derivative of the states.
28 | % y : double, 1 x 1
29 | % The output: steer angle [rad].
30 |
31 | deltal = x(1);
32 | omegal = x(2);
33 |
34 | Tm = u;
35 |
36 | dx = zeros(2, 1);
37 | dx(1) = omegal;
38 | dx(2) = (Tm - cl * omegal - tl * sign(omegal)) / ISF;
39 |
40 | y = deltal;
41 |
--------------------------------------------------------------------------------
/src/davisbicycle/perturbation_plot.py:
--------------------------------------------------------------------------------
1 | from math import sqrt
2 | import bicycledataprocessor.main as bdp
3 | import matplotlib.pyplot as plt
4 |
5 | pathToBicycle = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics'
6 | pathToDatabase = pathToBicycle + '/BicycleDataProcessor/InstrumentedBicycleData.h5'
7 | pathToParameters = pathToBicycle + '/BicycleParameters/data'
8 |
9 | dataset = bdp.DataSet(fileName=pathToDatabase)
10 | dataset.open()
11 |
12 | trialNum = '00312'
13 | trial = bdp.Run(trialNum, dataset.database, pathToParameters)
14 |
15 | dataset.close()
16 |
17 | golden_mean = (sqrt(5) - 1.0) / 2.0
18 | fig_width = 5.0
19 | fig_height = fig_width * golden_mean
20 | params = {'backend': 'ps',
21 | 'axes.labelsize': 10,
22 | 'text.fontsize': 10,
23 | 'legend.fontsize': 6,
24 | 'xtick.labelsize': 8,
25 | 'ytick.labelsize': 8,
26 | 'text.usetex': True,
27 | 'figure.figsize': [fig_width,fig_height],
28 | 'figure.dpi' : 200,
29 | 'figure.subplot.left' : 0.2,
30 | 'figure.subplot.bottom' : 0.15}
31 | plt.rcParams.update(params)
32 |
33 | latForce = trial.taskSignals['PullForce']
34 | time = latForce.time()
35 |
36 | fig = plt.figure()
37 | fig.suptitle(r'Run \#' + trialNum, fontsize=10)
38 |
39 | axFull = fig.add_subplot(1, 2, 1)
40 | axFull.plot(time, latForce)
41 | axFull.set_xlabel('Time [s]')
42 | axFull.set_ylabel('Force [N]')
43 |
44 | axZoom = fig.add_subplot(1, 2, 2)
45 | axZoom.plot(time, latForce)
46 | axZoom.set_xlim([11., 12.])
47 | axZoom.set_ylim([-50., 250.])
48 | axZoom.set_xlabel('Time [s]')
49 |
50 | fig.savefig('../../figures/davisbicycle/perturbation.png')
51 | fig.savefig('../../figures/davisbicycle/perturbation.pdf')
52 | fig.show()
53 |
--------------------------------------------------------------------------------
/src/davisbicycle/plot_processed.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import bicycledataprocessor as bdp
4 | import os
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 |
8 | pathToBicycleMechanics = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics'
9 | pathToDatabase = os.path.join(pathToBicycleMechanics, 'BicycleDataProcessor/InstrumentedBicycleData.h5')
10 | pathToParameterData = os.path.join(pathToBicycleMechanics, 'BicycleParameters/data')
11 |
12 | dataset = bdp.DataSet(fileName=pathToDatabase)
13 | dataset.open()
14 |
15 | trial = bdp.Run('00699', dataset,
16 | pathToParameterData=pathToParameterData, filterFreq=40.)
17 |
18 | dataset.close()
19 |
20 | golden_mean = (np.sqrt(5) - 1.0) / 2.0
21 | fig_width = 6.0
22 | fig_height = fig_width * golden_mean
23 | params = {'backend': 'ps',
24 | 'axes.labelsize': 8,
25 | 'axes.titlesize': 10,
26 | 'text.fontsize': 8,
27 | 'legend.fontsize': 6,
28 | 'xtick.labelsize': 8,
29 | 'ytick.labelsize': 8,
30 | 'text.usetex': True,
31 | 'figure.titlesize': 10,
32 | 'figure.figsize': [fig_width,fig_height],
33 | 'figure.dpi' : 200,
34 | 'figure.subplot.left' : 0.2,
35 | 'figure.subplot.bottom' : 0.15}
36 | plt.rcParams.update(params)
37 |
38 | sigs = ['PullForce', 'RollAngle', 'SteerAngle', 'SteerTorque']
39 |
40 | mx = {sig : abs(trial.taskSignals[sig]).max() for sig in sigs}
41 |
42 | plotStrings = [('%1.0f' % (mx['PullForce'] / v)) + '*' + k for k, v in mx.items()]
43 |
44 | fig = trial.plot(*plotStrings)
45 |
46 | fig.savefig('../../figures/davisbicycle/processed-data.png')
47 | fig.savefig('../../figures/davisbicycle/processed-data.pdf')
48 |
--------------------------------------------------------------------------------
/src/davisbicycle/roll_angle_trailer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | This script generates a rigid body model of the bicycle roll angle trailer and
5 | generates a graph showing the effect the height of the yoke axis on the yoke
6 | angle and roll angle.
7 | """
8 |
9 | import numpy as np
10 | from scipy.optimize import fsolve
11 | import matplotlib.pyplot as plt
12 | import sympy as sym
13 | import sympy.physics.mechanics as me
14 |
15 | # theta is the bicycle roll angle and beta is potentiometer angle
16 | theta, alpha, beta = sym.symbols('theta alpha beta')
17 |
18 | rr, h, l, rt = sym.symbols('rr h l rt')
19 |
20 | # newtonian reference frame
21 | N = me.ReferenceFrame('N', indices=('1', '2', '3'))
22 | # rear wheel rolls with respect to the newtonian frame
23 | R = N.orientnew('R', 'Axis', (theta, N['1']), indices=('1', '2', '3'))
24 | # the yolk pitches with respect to the wheel
25 | Y = R.orientnew('Y', 'Axis', (alpha, R['2']), indices=('1', '2', '3'))
26 | # the trailer rolls with respect to the yolk (beta is the potentiometer angle)
27 | T = Y.orientnew('T', 'Axis', (beta, Y['1']), indices=('1', '2', '3'))
28 |
29 | # rear wheel contact to the center of the trailer axle
30 | rAcNo = -rr * R['3'] + h * Y['3'] - l * Y['1'] - (h + rt - rr) * T['3']
31 |
32 | holo = []
33 | # the axle must be the wheel radius above the ground
34 | holo.append(rAcNo.dot(N['3']) + rt)
35 |
36 | # the trailer must be horizontal with respect to the ground
37 | holo.append(T['2'].dot(N['3']))
38 |
39 | def constrain(q, num, var, eq):
40 | subDict = {var[0] : q[0], # alpha
41 | var[1] : q[1], # beta
42 | var[2] : num[0], # theta
43 | var[3] : num[1], # rr
44 | var[4] : num[2], # h
45 | var[5] : num[3], # l
46 | var[6] : num[4]} # rt
47 |
48 | zero = np.zeros(2)
49 | for i, con in enumerate(eq):
50 | zero[i] = con.subs(subDict)
51 |
52 | return zero
53 | var = (alpha, beta, theta, rr, h, l, rt)
54 |
55 | thetaRange = np.linspace(-np.pi / 4.5, np.pi / 4.5, num=100)
56 | alphaBeta = np.zeros((len(thetaRange), 2))
57 |
58 | golden_mean = (np.sqrt(5) - 1.0) / 2.0
59 | fig_width = 4.0
60 | fig_height = fig_width * golden_mean
61 | params = {'backend': 'ps',
62 | 'axes.labelsize': 10,
63 | 'text.fontsize': 10,
64 | 'legend.fontsize': 6,
65 | 'xtick.labelsize': 8,
66 | 'ytick.labelsize': 8,
67 | 'text.usetex': True,
68 | 'figure.figsize': [fig_width,fig_height],
69 | 'figure.dpi' : 200,
70 | 'figure.subplot.left' : 0.2,
71 | 'figure.subplot.bottom' : 0.15}
72 | plt.rcParams.update(params)
73 |
74 | fig = plt.figure()
75 | ax1 = fig.add_subplot(2, 1, 1)
76 | ax1.set_ylabel(r'$\alpha$ [deg]')
77 | ax1.set_xlabel(r'$\theta$ [deg]')
78 | ax2 = fig.add_subplot(2, 1, 2)
79 | ax2.set_ylabel(r'$\beta$ [deg]')
80 | ax2.set_xlabel(r'$\theta$ [deg]')
81 |
82 | for height in [0., 0.05, 0.1, 0.15, 0.2, 0.25, 0.3]:
83 | for i, th in enumerate(thetaRange):
84 | # todo make these the correct values for the trailer that was built
85 | num = (th, 0.3, height, 0.5, 0.05)
86 | alphaBeta[i] = fsolve(constrain, [0., 0.], args=(num, var, holo))
87 |
88 | ax1.plot(np.rad2deg(thetaRange), np.rad2deg(alphaBeta[:, 0]),
89 | label=str(height))
90 | ax2.plot(np.rad2deg(thetaRange), np.rad2deg(alphaBeta[:, 1]),
91 | label=str(height))
92 |
93 | ax1.legend()
94 | ax2.legend()
95 |
96 | fig.savefig('../../figures/davisbicycle/trailer-angle.png', dpi=200)
97 | fig.savefig('../../figures/davisbicycle/trailer-angle.pdf')
98 | fig.show()
99 |
100 |
--------------------------------------------------------------------------------
/src/davisbicycle/spring_ode.m:
--------------------------------------------------------------------------------
1 | function [A, B, C, D, K, X0] = spring_ode(parameters, sampleTime, arg)
2 | % function [A, B, C, D, K, X0] = spring_ode(parameters, sampleTime, arg)
3 | %
4 | % Returns the innovations form of a simple undamped harmonic oscillator.
5 | %
6 | % Parameters
7 | % ----------
8 | % parameters : double
9 | % The single parameter, spring stiffness, in N/m.
10 | % sampleTime : double
11 | % Unused for this continous model.
12 | % arg : double
13 | % The fixed parameters, mass, in kg.
14 | %
15 | % Returns
16 | % -------
17 | % A : double, 2 x 2
18 | % The state matrix. The states are [distance, velocity].
19 | % B : double, 2 x 0
20 | % The input matrix. There are no inputs.
21 | % C : double, 1 x 2
22 | % The output matrix. The output is acceleration.
23 | % D : double, 1 x 0
24 | % The feedforward matrix. There are no inputs.
25 | % K : double, 2 x 1
26 | % The Kalman gain matrix.
27 | % X0 : double, 2 x 1
28 | % The initial states.
29 |
30 | k = parameters(1);
31 | m = arg;
32 |
33 | A = [0, 1; -k / m, 0];
34 | B = zeros(2, 0);
35 | C = [-k / m, 0];
36 | D = zeros(1, 0);
37 | K = zeros(2, 1);
38 | X0 = zeros(2, 1);
39 |
--------------------------------------------------------------------------------
/src/davisbicycle/spring_stiffness.m:
--------------------------------------------------------------------------------
1 | function [k, sigk] = spring_stiffness()
2 | % function [k, sigk] = spring_stiffness()
3 | %
4 | % Returns the estimated spring stiffness.
5 | %
6 | % Returns
7 | % -------
8 | % k : double
9 | % Mean stiffness [N/m].
10 | % sigk : double
11 | % The standard deviation in stiffness [N/m].
12 |
13 | runs = {'24', '25', '26'};
14 | stiffness = zeros(length(runs), 1);
15 |
16 | for i = 1:length(runs)
17 | [data, ~, ~] = steer_dynamics_iddata(['002' runs{i} '.mat'], [], []);
18 | % stiffness guess from Peter's static spring stiffness measurements
19 | k = 913; % N/m
20 | m = 11.4; % kg (the measured mass, not including any of the spring mass)
21 | grey = idgrey('spring_ode', k, 'c', m);
22 | set(grey, 'OutputName', data.OutputName)
23 | % this does not estimate the Kalman gain matrix (note that the stiffness
24 | % estimation will change from ~905 to ~901 if you do estimate K).
25 | fit = pem(data, grey, 'InitialState', 'Estimate');
26 | %figure(i)
27 | %compare(data, grey, fit, 1)
28 | stiffness(i) = fit.par;
29 | end
30 |
31 | k = mean(stiffness);
32 | sigk = std(stiffness);
33 |
--------------------------------------------------------------------------------
/src/davisbicycle/steer_dynamics_iddata.m:
--------------------------------------------------------------------------------
1 | function [data, delta0, omega0] = steer_dynamics_iddata(fileName, outputs, input)
2 | % function [data, delta0, omega0] = steer_dynamics_iddata(fileName, outputs,
3 | % input)
4 | %
5 | % Returns an iddata object with either no input or the steer tube torque as
6 | % the input and either or both the steer angle and steer rate measurements
7 | % as outputs.
8 | %
9 | % Parameters
10 | % ----------
11 | % fileName : char
12 | % The mat file name of the run (e.g. '00217.mat').
13 | % outputs : cell array of chars
14 | % The desired outputs. Options are 'SteerAngle' and 'SteerRate'.
15 | % input : boolean
16 | % True if you want the steer tube torque as the input and false if not
17 | % (i.e. no input.
18 | %
19 | % Returns
20 | % -------
21 | % data : iddata
22 | % The input/output data.
23 | % delta0 : double
24 | % The initial value of the steer angle.
25 | % omega0 : double
26 | % The initial value of the steer rate.
27 |
28 | pathToData = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleDataProcessor/exports/mat';
29 |
30 | runData = load([pathToData filesep fileName]);
31 |
32 | if ~strcmp(runData.Maneuver, 'Steer Dynamics Test')
33 | error([fileName ' is not a Steer Dynamics Test.'])
34 | end
35 |
36 | if ismember(fileName, {'00224.mat', '00225.mat', '00226.mat'})
37 | acceleration = runData.AccelerometerAccelerationZ;
38 | data = iddata(acceleration, [], 1 / 200);
39 | set(data, 'TimeUnit', 's')
40 | set(data, 'OutputName', 'Acceleration', 'OutputUnit', 'm/s^2')
41 | data = detrend(data);
42 | if data.OutputData(1) < 0
43 | start = find(data.OutputData > 0, 1);
44 | else
45 | start = find(data.OutputData < 0, 1);
46 | end
47 | data = data(start:end);
48 | delta0 = nan;
49 | omega0 = nan;
50 | else
51 | inchlb2Nm = 25.4 / 1000.0 * 4.44822162;
52 | Tm = inchlb2Nm * runData.SteerTubeTorque; % N*m
53 |
54 | delta = deg2rad(runData.SteerAngle);
55 | omega= runData.ForkRate; % rad/s
56 |
57 | y = [];
58 |
59 | if ismember('SteerRate', outputs)
60 | y = omega; % rad/s
61 | end
62 |
63 | if ismember('SteerAngle', outputs)
64 | y = [y, delta]; % rad
65 | end
66 |
67 | % the kinetic and potential energy of the system
68 | k = 912.77; % individual spring stiffness, N/m
69 | l = 0.231; % lever arm length, m
70 | % moment of inertia of wheel, fork and handlebar about steer axis, kg*m^2
71 | IH = 0.12974383;
72 | % differentiate the steer angle (this give a smoother signal than the using
73 | % the rate gyro, due to some time offset??)
74 | deltad = gradient(delta, 1 / 200);
75 | ke = 1 / 2 * IH * deltad.^2;
76 | pe = k * l^2 * delta.^2;
77 | energy = ke + pe;
78 | % use the data from 70% of max energy to 1% of max
79 | [maxE, ~] = max(energy);
80 | start = length(energy) - find(energy(end:-1:1) > maxE * 0.7, 1);
81 | stop = find(energy < maxE * 0.01, 1);
82 |
83 | if input
84 | data = iddata(y, Tm, 1 / 200);
85 | set(data, 'TimeUnit', 's')
86 | set(data, 'InputName', 'SteerTubeTorque', 'InputUnit', 'N*m')
87 | else
88 | data = iddata(y, [], 1 / 200);
89 | end
90 |
91 | unitMap.SteerAngle = 'rad';
92 | unitMap.SteerRate = 'rad/s';
93 | units = {};
94 | for i = 1:length(outputs)
95 | units{i} = unitMap.(outputs{i});
96 | end
97 |
98 | set(data, 'OutputName', outputs, 'OutputUnit', units)
99 |
100 | data = detrend(data(start:stop));
101 |
102 | delta0 = delta(start);
103 | omega0 = omega(start);
104 | end
105 |
--------------------------------------------------------------------------------
/src/davisbicycle/steer_torque_components.py:
--------------------------------------------------------------------------------
1 | import bicycledataprocessor as bdp
2 | import matplotlib.pyplot as plt
3 | from math import sqrt
4 |
5 | pathToBicycle = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics'
6 | pathToDatabase = pathToBicycle + '/BicycleDataProcessor/InstrumentedBicycleData.h5'
7 | pathToParameters = pathToBicycle + '/BicycleParameters/data'
8 |
9 | dataset = bdp.DataSet(fileName=pathToDatabase)
10 | dataset.open()
11 |
12 | trial = bdp.Run('00700', dataset, pathToParameters, forceRecalc=True)
13 |
14 | dataset.close()
15 |
16 | fig_width = 5.5
17 | fig_height = 7.5
18 | params = {'backend': 'ps',
19 | 'axes.labelsize': 10,
20 | 'text.fontsize': 10,
21 | 'legend.fontsize': 6,
22 | 'xtick.labelsize': 8,
23 | 'ytick.labelsize': 8,
24 | 'text.usetex': True,
25 | 'figure.figsize': [fig_width,fig_height],
26 | 'figure.dpi' : 200,
27 | 'figure.subplot.left' : 0.1,
28 | 'figure.subplot.bottom' : 0.1}
29 | plt.rcParams.update(params)
30 |
31 | fig = trial.compute_steer_torque(plot=True)
32 |
33 | fig.savefig('../../figures/davisbicycle/steer-torque-components.png')
34 | fig.savefig('../../figures/davisbicycle/steer-torque-components.pdf')
35 |
--------------------------------------------------------------------------------
/src/davisbicycle/time_sync.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This script plots the results of the time synchronization of the acceleration
4 | # signals caused by the bump.
5 |
6 | import bicycledataprocessor as bdp
7 | import os
8 | import numpy as np
9 | import matplotlib.pyplot as plt
10 |
11 | pathToBicycleMechanics = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics'
12 | pathToDatabase = os.path.join(pathToBicycleMechanics, 'BicycleDataProcessor/InstrumentedBicycleData.h5')
13 | pathToParameterData = os.path.join(pathToBicycleMechanics, 'BicycleParameters/data')
14 |
15 | dataset = bdp.DataSet(fileName=pathToDatabase)
16 | dataset.open()
17 |
18 | trial = bdp.Run('00690', dataset, pathToParameterData=pathToParameterData,
19 | forceRecalc=True)
20 |
21 | dataset.close()
22 |
23 | golden_mean = (np.sqrt(5) - 1.0) / 2.0
24 | fig_width = 4.0
25 | fig_height = fig_width * golden_mean
26 | params = {'backend': 'ps',
27 | 'axes.labelsize': 8,
28 | 'axes.titlesize': 10,
29 | 'text.fontsize': 8,
30 | 'legend.fontsize': 6,
31 | 'xtick.labelsize': 8,
32 | 'ytick.labelsize': 8,
33 | 'text.usetex': True,
34 | 'figure.figsize': [fig_width,fig_height],
35 | 'figure.dpi' : 200,
36 | 'figure.subplot.left' : 0.25,
37 | 'figure.subplot.bottom' : 0.15}
38 | plt.rcParams.update(params)
39 |
40 | # plot the data before it is synchronized
41 | unsync = trial.plot('-AccelerometerAccelerationY', 'AccelerationZ',
42 | signalType='calibrated')
43 |
44 | ax = unsync.axes[0]
45 | ax.set_xlim((0., 3.))
46 | ax.set_ylabel(r'Acceleration $\left[\frac{m}{s^2}\right]$')
47 | ax.set_xlabel('Time [s]')
48 | ax.set_title('Vertical Accelerometer Signals Before Synchronization')
49 |
50 | niLeg = ax.legend_.get_texts()[0]
51 | niLeg.set_text('NI USB-6218')
52 | vnLeg = ax.legend_.get_texts()[1]
53 | vnLeg.set_text('VN-100')
54 |
55 | unsync.savefig('../../figures/davisbicycle/unsync.png')
56 | unsync.savefig('../../figures/davisbicycle/unsync.pdf')
57 |
58 | # plot the data after synchronization
59 | sync = trial.plot('-AccelerometerAccelerationY', 'AccelerationZ',
60 | signalType='truncated')
61 |
62 | ax = sync.axes[0]
63 | ax.set_xlim((0., 3.))
64 | ax.set_ylabel(r'Acceleration $\left[\frac{m}{s^2}\right]$')
65 | ax.set_xlabel('Time [s]')
66 | ax.set_title('Vertical Accelerometer Signals After Synchronization')
67 |
68 | niLeg = ax.legend_.get_texts()[0]
69 | niLeg.set_text('NI USB-6218')
70 | vnLeg = ax.legend_.get_texts()[1]
71 | vnLeg.set_text('VN-100')
72 |
73 | sync.savefig('../../figures/davisbicycle/sync.png')
74 | sync.savefig('../../figures/davisbicycle/sync.pdf')
75 |
--------------------------------------------------------------------------------
/src/davisbicycle/torque-wrench-data.txt:
--------------------------------------------------------------------------------
1 | RunNumber,EstimatedSpeed,MaxNeedleTorque,Rider,Maneuver,MinSpeed,MaxSpeed,MaxTorque,MinTorque,RotationSense
2 | 1,5,16,Jason,HalfCircle10,6,6,16,-2.2,-
3 | 2,11,34,Jason,HalfCircle10,10,10,34,-3.2,-
4 | 3,15,15,Jason,HalfCircle10,13,15,13,-2.6,-
5 | 4,15,25,Jason,HalfCircle10,12,15,7,-2.6,-
6 | 5,5,NA,Luke,HalfCircle10,5,5,24,-2.6,-
7 | 6,10,NA,Luke,HalfCircle10,9,9,25,-1.2,-
8 | 7,5,25,Luke,HalfCircle10,4,5,25,-2.2,-
9 | 8,11,16,Luke,HalfCircle10,8,9,16,-1.2,-
10 | 9,15,24,Luke,HalfCircle10,12,12,15,-0.8,-
11 | 10,15,35,Luke,HalfCircle10,15,15,10,-2.4,-
12 | 11a,NA,NA,Luke,HalfCircle6,5,8,15,-1.8,+
13 | 11b,NA,NA,Luke,HalfCircle6,6,6,30,-5,+
14 | 12,5,30,Luke,HalfCircle6,6,7,30,-3.6,+
15 | 13,5,30,Luke,HalfCircle6,7,7,23,-2.2,+
16 | 14,9,NA,Luke,HalfCircle6,7,10,15,-2.0,+
17 | 15,5,30,Jason,HalfCircle6,6,9,14,-2.2,+
18 | 16,5,32,Jason,HalfCircle6,4,7,22,-3.0,+
19 | 17,10,39,Jason,HalfCircle6,6,12,15,-1.0,+
20 | 18,5,23,Jason,LineTrack,5,5,23,-2.8,NA
21 | 19,5,11,Jason,LineTrack,5,5,11,-3.4,NA
22 | 20,11,21,Jason,LineTrack,10,11,21,-2.2,NA
23 | 21,11,16,Jason,LineTrack,11,12,16,-1.6,NA
24 | 22,5,19,Jason,LaneChange,4,4,19,-2.6,-
25 | 23,11,35,Jason,LaneChange,11,12,20,-2.0,-
26 | 24,11,31,Jason,LaneChange,11,12,23,-2.0,-
27 | 25,5,17,Luke,LineTrack,5,5,17,-2.2,NA
28 | 26,5,14,Luke,LineTrack,6,6,14,-1.6,NA
29 | 27,10,15,Luke,LineTrack,8,9,10,-1.2,NA
30 | 28,10,20,Luke,LineTrack,9,10,20,-1.0,NA
31 | 29,5,25,Luke,LaneChange,5,5,25,-1.4,-
32 | 30,5,20,Luke,LaneChange,5,5,20,-2.4,-
33 | 31,10,16,Luke,LaneChange,10,11,16,-2.2,-
34 | 32,10,26,Luke,LaneChange,9,10,26,-2.0,-
35 | 33,5,36,Jason,Slalom,5,5,36,-2.8,NA
36 | 34,5,22,Jason,Slalom,5,5,30,-3.2,NA
37 | 35,8,32,Jason,Slalom,8,8,32,-3.0,NA
38 | 36,8,29,Jason,Slalom,7,7,29,-2.0,NA
39 | 37,10,40,Jason,Slalom,9,9,40,-3.0,NA
40 | 38,10,40,Jason,Slalom,9,9,40,-3.0,NA
41 | 39,5,35,Luke,Slalom,4,4,35,-4.8,NA
42 | 40,5,31,Luke,Slalom,4,4,31,-3.2,NA
43 | 41,8,35,Luke,Slalom,8,8,35,-4.4,NA
44 | 42,8,29,Luke,Slalom,8,8,29,-4.0,NA
45 | 43,10,34,Luke,Slalom,10,11,34,-3.6,NA
46 | 44,10,32,Luke,Slalom,10,11,32,-3.0,NA
47 | 45,5,30,Jason,Circle10,5,5,30,-1.6,-
48 | 46,5,15,Jason,Circle10,5,6,15,-2.0,+
49 | 47,5,19,Jason,Circle10,5,5,25,-2.4,+
50 | 48,10,19,Jason,Circle10,9,10,15,-2.4,+
51 | 49,13,16,Jason,Circle10,12,13,20,-2.0,+
52 | 50,10,19,Jason,Circle10,10,10,19,-1.2,-
53 | 51,13,20,Jason,Circle10,12,13,20,-1.2,-
54 | 52,5,24,Jason,Circle5,4,5,20,-2.2,-
55 | 53,5,18,Jason,Circle5,4,4,7,-2.2,+
56 | 54,8,21,Jason,Circle5,8,8,21,-1.0,-
57 | 55,8,16,Jason,Circle5,7,8,9,-2.0,+
58 | 56,10,9,Jason,Circle5,9,10,15,-1.2,+
59 | 57,10,15,Jason,Circle5,10,11,15,-1.2,-
60 | 58,5,17,Jason,LineTrack,5,5,17,-1.4,NA
61 | 59,11,19,Jason,LineTrack,10,10,15,-2.0,NA
62 | 60,13,NA,Jason,LineTrack,13,13,11,-1.2,NA
63 | 61,13,NA,Jason,LineTrack,NA,NA,NA,NA,NA
64 | 62,8,15,Jason,LineTrack,8,8,15,-1.6,NA
65 | 63,13,17,Jason,LineTrack,13,13,15,-2.0,NA
66 | 64,14,NA,Jason,LineTrack,14,14,20,-1.6,NA
67 | 65,10,18,Jason,LineTrack,10,11,18,-2.0,NA
68 | 66,6,16,Jason,LineTrack,6,6,16,-2.0,NA
69 |
--------------------------------------------------------------------------------
/src/davisbicycle/torque-wrench.R:
--------------------------------------------------------------------------------
1 | # Filename: torque-wrench.R
2 | # Date: April 6, 2010
3 | # Author: Jason Moore
4 | # Description: Analyzes the data from the bicycle torque wrench experiments
5 | # performed on April 6, 2010.
6 |
7 | rm(list = ls())
8 |
9 | # read the data
10 | data <- read.csv('torque-wrench-data.txt')
11 |
12 | # miles per hour to meters per second
13 | mph2mps = 0.44704
14 | # inch pounds to newton meters
15 | inchlb2nm = 0.112984829
16 |
17 | averageSpeed <- (data$MaxSpeed + data$MinSpeed) / 2
18 |
19 | "../../figures/davisbicycle/" -> figDir
20 |
21 | # histogram of the average run speeds
22 | png(paste(figDir, "twrench-speed-histogram.png", sep=""),
23 | width=800, height=800, pointsize=10, res=200)
24 | hist(averageSpeed)
25 | dev.off()
26 |
27 | # histogram of the max/min torque values
28 | png(paste(figDir, "twrench-torque-histogram.png", sep=""),
29 | width=800, height=800, pointsize=10, res=200)
30 | hist(abs(c(data$MinTorque, inchlb2nm * data$MaxTorque)), main ="Histogram
31 | of Torque Values", xlab="Absolute value of max and min torques [Nm]", breaks=25)
32 | dev.off()
33 |
34 | # torque versus speed for all the runs
35 | png(paste(figDir, "twrench-torque-speed.png", sep=""),
36 | width=800, height=800, pointsize=10, res=200)
37 | plot(0:10,-5:5, type="n", main="Max and Min Torques as a Function of Speed",
38 | xlab="Speed [m/s]", ylab="Torque [Nm]")
39 | points(mph2mps * averageSpeed, data$MinTorque, pch=19)
40 | points(mph2mps * averageSpeed, inchlb2nm * data$MaxTorque, pch=23)
41 | dev.off()
42 |
43 | maneuvers <- unique(data$Maneuver)
44 | print(maneuvers)
45 | for(maneuver in levels(maneuvers)){
46 | print(maneuver)
47 | png(paste(figDir, "twrench-", maneuver, ".png", sep=""))
48 | x <- subset(mph2mps*averageSpeed, data$Maneuver==maneuver)
49 | y <- subset(data$MinTorque, data$Maneuver==maneuver)
50 | y2 <- subset(inchlb2nm*data$MaxTorque, data$Maneuver==maneuver)
51 | plot(0:10,-5:5, type="n", main=maneuver, xlab="Speed [m/s]", ylab="Torque [Nm]")
52 | points(x, y)
53 | points(x, y2)
54 | arrows(x, y, x, y2, length=0)
55 | dev.off()
56 | maxTorque <- max(y2, na.rm=TRUE)
57 | minTorque <- min(y, na.rm=TRUE)
58 | print(paste("Max and min torque for ", maneuver, " = ", maxTorque, " and ",
59 | minTorque))
60 | }
61 |
--------------------------------------------------------------------------------
/src/davisbicycle/total_bearing_friction.m:
--------------------------------------------------------------------------------
1 | function [cb, tb, IHF, k, l] = total_bearing_friction(varargin)
2 | % function [cb, tb, IHF, k, l] = total_bearing_friction(varargin)
3 | %
4 | % Returns estimated parameters for the upper bearing friction in the steer
5 | % column.
6 | %
7 | % Parameters
8 | % ----------
9 | % varargin : cell array of booleans, 1 x 5, optional
10 | % Specify true or false for each parameters. True will fix the parameter
11 | % and false will allow it to be estimated.
12 | %
13 | % Returns
14 | % -------
15 | % cb : double, 15 x 1
16 | % The viscous friction coefficient [N*m*s].
17 | % tb : double, 15 x 1
18 | % The Coulomb friction torque [N*m].
19 | % IHF : double, 15 x 1
20 | % The moment of inertia of the handlebar, fork and front wheel about the
21 | % steer axis [kg*m^2].
22 | % k : double, 15 x 1
23 | % The spring stiffness [N/m].
24 | % l : double, 15 x 1
25 | % The lever arm [m].
26 |
27 | % set the initial guesses and bounds on the parameters
28 | par(1, 1).Name = 'cb';
29 | par(1, 1).Unit = '';
30 | par(1, 1).Value = 0.4327;
31 | par(1, 1).Minimum = 0;
32 | par(1, 1).Maximum = Inf;
33 | par(1, 1).Fixed = 0;
34 |
35 | par(2, 1).Name = 'tb';
36 | par(2, 1).Unit = '';
37 | par(2, 1).Value = 0.0349;
38 | par(2, 1).Minimum = 0;
39 | par(2, 1).Maximum = Inf;
40 | par(2, 1).Fixed = 0;
41 |
42 | % handlebar, fork and front wheel inertia about the steer axis
43 | par(3, 1).Name = 'IHF';
44 | par(3, 1).Unit = 'kg*m^2';
45 | par(3, 1).Value = 0.12974383;
46 | par(3, 1).Minimum = 0;
47 | par(3, 1).Maximum = Inf;
48 | par(3, 1).Fixed = 1;
49 |
50 | % spring stiffness, N/m
51 | par(4, 1).Name = 'k';
52 | par(4, 1).Unit = 'N/m';
53 | par(4, 1).Value = 904.7;
54 | par(4, 1).Minimum = 0;
55 | par(4, 1).Maximum = Inf;
56 | par(4, 1).Fixed = 1;
57 |
58 | % spring lever arm, m
59 | par(5, 1).Name = 'l';
60 | par(5, 1).Unit = 'm';
61 | par(5, 1).Value = 0.231;
62 | par(5, 1).Minimum = 0;
63 | par(5, 1).Maximum = Inf;
64 | par(5, 1).Fixed = 1;
65 |
66 | % set the initial conditions
67 | init(1, 1).Name = 'delta';
68 | init(1, 1).Value = 0;
69 | init(1, 1).Unit = '';
70 | init(1, 1).Minimum = -Inf;
71 | init(1, 1).Maximum = Inf;
72 | init(1, 1).Fixed = 0;
73 |
74 | init(2, 1).Name = 'omega';
75 | init(2, 1).Value = 0;
76 | init(2, 1).Unit = '';
77 | init(2, 1).Minimum = -Inf;
78 | init(2, 1).Maximum = Inf;
79 | init(2, 1).Fixed = 0;
80 |
81 | % build the nonlinear grey box model
82 | order = [1, 0, 2]; % outputs, inputs, states
83 | sampleTime = 0; % zero means it is defined as a continous model
84 | grey = idnlgrey('total_ode', order, par, init, sampleTime);
85 | set(grey, 'OutputName', 'SteerAngle')
86 |
87 | % all of the steer tests
88 | runs = 209:223;
89 |
90 | cb = zeros(length(runs), 1);
91 | tb = zeros(length(runs), 1);
92 | IHF = zeros(length(runs), 1);
93 | k = zeros(length(runs), 1);
94 | l = zeros(length(runs), 1);
95 |
96 | for i = 1:length(runs)
97 | display(['Run 00' num2str(runs(i))])
98 |
99 | [data, delta0, omega0] = steer_dynamics_iddata(['00' num2str(runs(i)) ...
100 | '.mat'], {'SteerAngle'}, false);
101 |
102 | % if fixed is supplied apply it
103 | if size(varargin, 2) > 0
104 | if size(varargin{1}, 2) ~= 5
105 | error('You must supply five booleans.')
106 | else
107 | setpar(grey, 'Fixed', varargin{1})
108 | end
109 | end
110 |
111 | % update the initial condition guesses
112 | setinit(grey, 'Value', {delta0, omega0})
113 |
114 | % find the best fit
115 | fit = pem(data, grey, 'InitialState', 'Estimate');
116 |
117 | % store the results
118 | cb(i) = fit.Par(1).Value;
119 | tb(i) = fit.Par(2).Value;
120 | IHF(i) = fit.Par(3).Value;
121 | k(i) = fit.Par(4).Value;
122 | l(i) = fit.Par(5).Value;
123 |
124 | % show the plots
125 | figure(i)
126 | % you have to give a finite prediction horizon for time series models (no input)
127 | compare(data, grey, fit, 1);
128 | end
129 |
--------------------------------------------------------------------------------
/src/davisbicycle/total_ode.m:
--------------------------------------------------------------------------------
1 | function [dx, y] = total_ode(t, x, u, cb, tb, IHF, k, l, varargin)
2 | % function [dx, y] = total_ode(t, x, u, cb, tb, IHF, k, l, varargin)
3 | %
4 | % Returns the state derivatives and the outputs of the handlebar, fork and
5 | % front wheel assembly motion.
6 | %
7 | % Parameters
8 | % ----------
9 | % t : double
10 | % Current time [s].
11 | % x : double, 2 x 1
12 | % Current state [deltal; omegal].
13 | % u : double, 0 x 0
14 | % Empty matrix.
15 | % cb : double
16 | % Viscous friction coefficient [N*m*s].
17 | % tb : double
18 | % Coulomb friction coefficient [N*m].
19 | % IHF : double
20 | % Handlebar, fork and front wheel moment of inertia about the steer axis
21 | % [kg*m^2].
22 | % k : double, 7 x 1
23 | % The spring stiffness [N/m].
24 | % l : double, 7 x1
25 | % The lever arm [m].
26 | % varargin : cell array
27 | % Optional arguments passed through the idnlgrey/pem calls.
28 | %
29 | % Returns
30 | % -------
31 | % dx : double, 2 x 1
32 | % The derivative of the states.
33 | % y : double, 1 x 1
34 | % The output: steer angle [rad].
35 |
36 | delta = x(1);
37 | omega = x(2);
38 |
39 | dx = zeros(2, 1);
40 | dx(1) = omega;
41 | dx(2) = (-cb * omega - tb * sign(omega) - 2 * k * l^2 * delta) / IHF;
42 |
43 | y = delta;
44 |
--------------------------------------------------------------------------------
/src/davisbicycle/upper_bearing_friction.m:
--------------------------------------------------------------------------------
1 | function [cu, tu, IG, k, l] = upper_bearing_friction(varargin)
2 | % function [cu, tu, IG, k, l] = upper_bearing_friction(varargin)
3 | %
4 | % Returns estimated parameters for the upper bearing friction in the steer
5 | % column.
6 | %
7 | % Parameters
8 | % ----------
9 | % varargin : cell array of booleans, 1 x 5, optional
10 | % Specify true or false for each parameters. True will fix the parameter
11 | % and false will allow it to be estimated.
12 | %
13 | % Returns
14 | % -------
15 | % cu : double, 7 x 1
16 | % The viscous friction coefficient in N*m*s.
17 | % tu : double, 7 x 1
18 | % The Coulomb friction torque in N*m.
19 | % IG : double, 7 x 1
20 | % The moment of inertia of the handlebar (above the steer torque sensor)
21 | % about the steer axis in kg*m^2.
22 | % k : double, 7 x 1
23 | % The spring stiffness.
24 | % l : double, 7 x1
25 | % The lever arm.
26 |
27 | % set the initial guesses and bounds on the parameters
28 | par(1, 1).Name = 'cu';
29 | par(1, 1).Unit = '';
30 | par(1, 1).Value = 0.4327;
31 | par(1, 1).Minimum = 0;
32 | par(1, 1).Maximum = Inf;
33 | par(1, 1).Fixed = 0;
34 |
35 | par(2, 1).Name = 'tu';
36 | par(2, 1).Unit = '';
37 | par(2, 1).Value = 0.0349;
38 | par(2, 1).Minimum = 0;
39 | par(2, 1).Maximum = Inf;
40 | par(2, 1).Fixed = 0;
41 |
42 | % handlebar moment of inertia about steer axis
43 | % the inertia is from the bicycle parameters measurements
44 | par(3, 1).Name = 'IG';
45 | par(3, 1).Unit = 'kg*m^2';
46 | par(3, 1).Value = 0.06569374;
47 | par(3, 1).Minimum = 0;
48 | par(3, 1).Maximum = Inf;
49 | par(3, 1).Fixed = 1;
50 |
51 | % spring stiffness, N/m
52 | par(4, 1).Name = 'k';
53 | par(4, 1).Unit = 'N/m';
54 | par(4, 1).Value = 904.7;
55 | par(4, 1).Minimum = 0;
56 | par(4, 1).Maximum = Inf;
57 | par(4, 1).Fixed = 1;
58 |
59 | % spring lever arm, m
60 | par(5, 1).Name = 'l';
61 | par(5, 1).Unit = 'm';
62 | par(5, 1).Value = 0.231;
63 | par(5, 1).Minimum = 0;
64 | par(5, 1).Maximum = Inf;
65 | par(5, 1).Fixed = 1;
66 |
67 | % set the initial conditions
68 | init(1, 1).Name = 'deltau';
69 | init(1, 1).Value = 0;
70 | init(1, 1).Unit = '';
71 | init(1, 1).Minimum = -Inf;
72 | init(1, 1).Maximum = Inf;
73 | init(1, 1).Fixed = 0;
74 |
75 | init(2, 1).Name = 'omegau';
76 | init(2, 1).Value = 0;
77 | init(2, 1).Unit = '';
78 | init(2, 1).Minimum = -Inf;
79 | init(2, 1).Maximum = Inf;
80 | init(2, 1).Fixed = 0;
81 |
82 | % build the nonlinear grey box model
83 | % I did have upper_ode as a nested function here and called idnlgrey
84 | % with @upper_ode, but got some kind of error that wouldn't let it
85 | % simulate. Then I moved upper_ode to another function and it worked
86 | % fine. This seems like a bug as the docs say you can use function handles.
87 | grey = idnlgrey('upper_ode', [1, 1, 2], par, init, 0);
88 | set(grey, 'InputName', 'SteerTubeTorque')
89 | set(grey, 'OutputName', 'SteerRate')
90 |
91 | % runs 217-223 have the rate gyro mounted to the top of the steer tube
92 | runs = 217:223;
93 |
94 | cu = zeros(length(runs), 1);
95 | tu = zeros(length(runs), 1);
96 | IG = zeros(length(runs), 1);
97 | k = zeros(length(runs), 1);
98 | l = zeros(length(runs), 1);
99 |
100 | for i = 1:length(runs)
101 | % Only fit to the rate gyro output as it was attached to the top of the
102 | % handlebar. This tries to eliminate any flexibility issues in the steer
103 | % column by not using the steer angle sensor which is further away.
104 | [data, deltaNaught] = steer_dynamics_iddata(['00' num2str(runs(i)) ...
105 | '.mat'], {'SteerRate'}, true);
106 |
107 | % if fixed is supplied apply it
108 | if size(varargin, 2) > 0
109 | if size(varargin{1}, 2) ~= 5
110 | error('You must supply five booleans.')
111 | else
112 | setpar(grey, 'Fixed', varargin{1})
113 | end
114 | end
115 |
116 | % update the initial condition guesses
117 | setinit(grey, 'Value', {deltaNaught, data.OutputData(1)})
118 |
119 | % find the best fit
120 | fit = pem(data, grey, 'InitialState', 'Estimate');
121 |
122 | % store the results
123 | cu(i) = fit.Par(1).Value;
124 | tu(i) = fit.Par(2).Value;
125 | IG(i) = fit.Par(3).Value;
126 | k(i) = fit.Par(4).Value;
127 | l(i) = fit.Par(5).Value;
128 |
129 | % show the plots
130 | figure(i)
131 | compare(data, grey, fit);
132 | end
133 |
--------------------------------------------------------------------------------
/src/davisbicycle/upper_ode.m:
--------------------------------------------------------------------------------
1 | function [dx, y] = upper_ode(t, x, u, cu, tu, IG, k, l, varargin)
2 | % function [dx, y] = upper_ode(t, x, u, cu, tu, IG, k, l, varargin)
3 | %
4 | % Returns the state derivatives and the outputs of the handlebar motion as a
5 | % function of time.
6 | %
7 | % Parameters
8 | % ----------
9 | % t : double
10 | % Current time [s].
11 | % x : double, 2 x 1
12 | % Current state [deltau; omegau].
13 | % u : double
14 | % Current input, Tm [Nm].
15 | % cu : double
16 | % Viscous friction coefficient [N*m*s].
17 | % tu : double
18 | % Coulomb friction coefficient [N*m].
19 | % IG : double
20 | % Handlebar moment of inertia about the steer axis [kg*m^2].
21 | % k : double
22 | % Spring stiffness [N/m].
23 | % l : double
24 | % Lever arm [m].
25 | % varargin : cell array
26 | % Optional arguments passed through the idnlgrey/pem calls.
27 | %
28 | % Returns
29 | % -------
30 | % dx : double, 2 x 1
31 | % The derivative of the states.
32 | % y : double
33 | % The output, steer rate [rad/s].
34 |
35 | deltau = x(1);
36 | omegau = x(2);
37 |
38 | Tm = u;
39 |
40 | dx = zeros(2, 1);
41 | dx(1) = omegau;
42 | dx(2) = (-cu * omegau - tu * sign(omegau) - 2 * k * l^2 * deltau - Tm) / IG;
43 |
44 | y = omegau;
45 |
--------------------------------------------------------------------------------
/src/defaults.cfg:
--------------------------------------------------------------------------------
1 | [data]
2 | base = /media/Data/Documents/School/UC Davis/Bicycle Mechanics
3 | pathToDatabase = %(base)s/BicycleDataProcessor/InstrumentedBicycleData.h5
4 | pathToCorruption = %(base)s/BicycleDataProcessor/data-corruption.csv
5 | pathToRunMat = %(base)s/BicycleDAQ/data
6 | pathToCalibMat = %(base)s/BicycleDAQ/data/CalibData
7 | pathToRunH5 = %(base)s/BicycleDAQ/data/h5
8 | pathToCalibH5 = %(base)s/BicycleDAQ/data/CalibData
9 | pathToParameters = %(base)s/BicycleParameters/data
10 | pathToCanonIdData = %(base)s/CanonicalBicycleID/data
11 | pathToIdMat = %(base)s/CanonicalBicycleID/data/idMatrices.p
12 | pathToCovarMat = %(base)s/CanonicalBicycleID/data/covMatrices.p
13 | pathToGoodRuns = %(base)s/CanonicalBicycleID/data/goodRuns.p
14 |
--------------------------------------------------------------------------------
/src/eom/WhippleDynamics.in:
--------------------------------------------------------------------------------
1 | File: WhippleDynamics.in
2 | -----------------------------------------------------------------------------
3 | Note: Use spaces not tabs while editing this file.
4 | ----------------------------------------------------------+------------------
5 | Description Quantity Units | Value
6 | ----------------------------------------------------------|------------------
7 | Constant d1 UNITS | 0.0
8 | Constant d2 UNITS | 0.0
9 | Constant d3 UNITS | 0.0
10 | Constant g UNITS | 0.0
11 | Constant ic11 UNITS | 0.0
12 | Constant ic22 UNITS | 0.0
13 | Constant ic31 UNITS | 0.0
14 | Constant ic33 UNITS | 0.0
15 | Constant id11 UNITS | 0.0
16 | Constant id22 UNITS | 0.0
17 | Constant ie11 UNITS | 0.0
18 | Constant ie22 UNITS | 0.0
19 | Constant ie31 UNITS | 0.0
20 | Constant ie33 UNITS | 0.0
21 | Constant if11 UNITS | 0.0
22 | Constant if22 UNITS | 0.0
23 | Constant l1 UNITS | 0.0
24 | Constant l2 UNITS | 0.0
25 | Constant l3 UNITS | 0.0
26 | Constant l4 UNITS | 0.0
27 | Constant mc UNITS | 0.0
28 | Constant md UNITS | 0.0
29 | Constant me UNITS | 0.0
30 | Constant mf UNITS | 0.0
31 | Constant rf UNITS | 0.0
32 | Constant rr UNITS | 0.0
33 |
34 | Initial Value q1 m | 0.0
35 | Initial Value q2 m | 0.0
36 | Initial Value q3 rad | 0.0
37 | Initial Value q4 rad | 0.0
38 | Initial Value q5 rad | 0.0
39 | Initial Value q6 rad | 0.0
40 | Initial Value q7 rad | 0.0
41 | Initial Value q8 rad | 0.0
42 | Initial Value u4 rad/s | 0.0
43 | Initial Value u6 rad/s | 0.0
44 | Initial Value u7 rad/s | 0.0
45 |
46 | Initial Time TINITIAL sec | 0.0
47 | Final Time TFINAL sec | 1.0
48 | Integration Step INTEGSTP sec | 0.1
49 | Print-Integer PRINTINT Positive Integer| 1
50 | Absolute Error ABSERR | 1.0E-08
51 | Relative Error RELERR | 1.0E-07
52 | ----------------------------------------------------------+------------------
53 |
54 | Note:
55 |
56 | INTEGSTP*PRINTINT is the time-interval for writing of output.
57 |
58 | ABSERR should be set equal to 10^(-8)*Xsmall, where Xsmall is the estimated
59 | smallest maximum absolute value of the variables being integrated.
60 |
61 | RELERR should be set equal to 10^(-d), where d is the desired number of
62 | significant digits of numerical integration results.
63 |
--------------------------------------------------------------------------------
/src/eom/basu_comparison.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file computes the derivatives of the coordinates and speeds for a single
4 | # state provided by Table 1 in BasuMandall2007 with my model and compares the
5 | # results.
6 |
7 | # dependencies
8 | import numpy as np
9 | import bicycleparameters as bp
10 | from dtk import bicycle
11 |
12 | # local dependencies
13 | try:
14 | f = open('Whipple.py', 'r')
15 | except IOError:
16 | from altk import alparse
17 | alparse.alparse('Whipple', 'Whipple', code='Python')
18 | else:
19 | f.close()
20 | del f
21 |
22 | from Whipple import Whipple
23 |
24 | # create the Whipple model object
25 | whip = Whipple()
26 |
27 | # load the benchmark parameters
28 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
29 | benchmark = bp.Bicycle('Benchmark', pathToData)
30 | benchmarkPar = bp.io.remove_uncertainties(benchmark.parameters['Benchmark'])
31 |
32 | # convert to my parameter set
33 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
34 |
35 | # set the parameters
36 | whip.parameters = moorePar
37 | whip.constants() # make sure the constants are recalculated with the new parameters
38 |
39 | # load the input values specified in table one of Basu-Mandal2007
40 | basuInput = bicycle.basu_table_one_input()
41 |
42 | # convert the values to my coordinates and speeds
43 | mooreInput = bicycle.basu_to_moore_input(basuInput, benchmarkPar['rR'],
44 | benchmarkPar['lam'])
45 |
46 | # store them in a state array
47 | x = np.array([mooreInput[x] for x in whip.stateNames])
48 |
49 | # calculate the derivatives of the state using my model and compare it to
50 | # BasuMandal2007
51 | xd = whip.f(x, 0.)
52 | y = whip.outputs(x)
53 |
54 | # convert the outputs from my model to the Basu-Mandal coordinates
55 | mooreOutput = {k : v for k, v in zip(whip.outputNames, y)}
56 | mooreOutputBasu = bicycle.moore_to_basu(mooreOutput, benchmarkPar['rR'], benchmarkPar['lam'])
57 | basuOutput = bicycle.basu_table_one_output()
58 |
59 | # make an rst list table
60 | def rst_math(s):
61 | """Returns the variable name as reStructuredText math notation."""
62 |
63 | mapping = {'x': r'x',
64 | 'y': r'y',
65 | 'z': r'z',
66 | 'theta': r'\theta',
67 | 'psi': r'\psi',
68 | 'phi': r'\phi',
69 | 'psif': r'\psi_f',
70 | 'betar': r'\beta_r',
71 | 'betaf': r'\beta_f'}
72 | if s.endswith('dd'):
73 | s = r'\ddot{' + mapping[s[:-2]] + '}'
74 | elif s.endswith('d'):
75 | s = r'\dot{' + mapping[s[:-1]] + '}'
76 | else:
77 | s = mapping[s]
78 |
79 | return ':math:`' + s + '`'
80 |
81 | table = """.. list-table:: Nonlinear Whipple Model Comparision
82 | :header-rows: 1
83 |
84 | * - Variable
85 | - Basu-Mandal
86 | - Moore\n"""
87 |
88 | variables = basuInput.keys() + basuOutput.keys()
89 | variables.sort()
90 | sigFigs = bicycle.basu_sig_figs()
91 |
92 | for v in variables:
93 | sig = '%1.' + str(sigFigs[v] - 1) + 'e'
94 | try:
95 | basu = basuOutput[v]
96 | except KeyError:
97 | basu = basuInput[v]
98 | finally:
99 | if basu == 0:
100 | basu = '0'
101 | moore = '0'
102 | else:
103 | basu = sig % basu
104 | moore = sig % mooreOutputBasu[v]
105 | if basu.endswith('e+00'):
106 | basu = basu[:-4]
107 | if moore.endswith('e+00'):
108 | moore = moore[:-4]
109 | table += (' ' * 3 + '* - ' + rst_math(v) + '\n' + ' ' * 5 + '- ' + basu +
110 | '\n' + ' ' * 5 + '- ' + moore + '\n')
111 |
112 | print('These are comparisons from BasuMandal2007 table one.')
113 | print table
114 |
115 | with open('../../tables/eom/nonlin.rst', 'w') as f:
116 | f.write(table)
117 |
--------------------------------------------------------------------------------
/src/eom/crescendo_eigenvectors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # dependencies
4 | import numpy as np
5 | import bicycleparameters as bp
6 | from dtk import bicycle
7 |
8 | # create the Whipple model (with my parameters)
9 | try:
10 | f = open('Whipple.py', 'r')
11 | except IOError:
12 | from altk import alparse
13 | alparse.alparse('Whipple', 'Whipple', code='Python')
14 | else:
15 | f.close()
16 | del f
17 |
18 | # local dependencies
19 | from Whipple import LinearWhipple
20 |
21 | whip = LinearWhipple()
22 |
23 | # load the benchmark parameters
24 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
25 | crescendo = bp.Bicycle('Crescendo', pathToData=pathToData, forceRawCalc=True)
26 | benchmarkPar = bp.io.remove_uncertainties(crescendo.parameters['Benchmark'])
27 | # convert to my parameter set
28 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
29 | whip.set_parameters(moorePar)
30 |
31 | # linearize about the nominal configuration
32 | equilibrium = np.zeros(len(whip.stateNames))
33 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
34 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
35 | equilibrium[whip.stateNames.index('q5')] = pitchAngle
36 | speedNaught = 1.5
37 | u6Naught = -speedNaught / moorePar['rr']
38 | equilibrium[whip.stateNames.index('u6')] = u6Naught
39 | whip.linear(equilibrium)
40 |
41 | figs = whip.plot_eigenvectors(states=('u4', 'u7'), pub=True)
42 |
43 | for i, f in enumerate(figs):
44 | speed = '1p5'
45 | f.savefig('../../figures/parameterstudy/cres-evec-' + speed + '-' + str(i + 1) + '.png')
46 |
--------------------------------------------------------------------------------
/src/eom/linear_comparison.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # dependencies
4 | import numpy as np
5 | import bicycleparameters as bp
6 | from dtk import bicycle
7 |
8 | # local dependencies
9 | try:
10 | f = open('Whipple.py', 'r')
11 | except IOError:
12 | from altk import alparse
13 | alparse.alparse('Whipple', 'Whipple', code='Python')
14 | else:
15 | f.close()
16 | del f
17 |
18 | from Whipple import LinearWhipple
19 |
20 | # create the Whipple model (with my parameters)
21 | whip = LinearWhipple()
22 |
23 | # load the benchmark parameters
24 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
25 | benchmark = bp.Bicycle('Benchmark', pathToData)
26 | benchmarkPar = bp.io.remove_uncertainties(benchmark.parameters['Benchmark'])
27 | # convert to my parameter set
28 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
29 | whip.set_parameters(moorePar)
30 |
31 | # set the initial conditions to match Meijaard2007
32 | speedNaught = 4.6
33 | u6Naught = -speedNaught / moorePar['rr']
34 | rollRateNaught = 0.5
35 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
36 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
37 |
38 | # linearize about the nominal configuration
39 | equilibrium = np.zeros(len(whip.stateNames))
40 | equilibrium[whip.stateNames.index('q5')] = pitchAngle
41 | equilibrium[whip.stateNames.index('u6')] = u6Naught
42 | whip.linear(equilibrium)
43 |
44 | # set up the simulation
45 | whip.set_initial_conditions('q5', pitchAngle, 'u4', rollRateNaught, 'u6', u6Naught)
46 |
47 | # integration settings
48 | ts = 0.01 # step size
49 | tf = 5. # final time
50 | whip.intOpts['ts'] = ts
51 | whip.intOpts['tf'] = tf
52 |
53 | whip.simulate()
54 |
55 | # plot figure 4 from Meijaard2007 using my model
56 | rollRate = whip.get_sim_output('u4')
57 | speed = -whip.get_sim_output('u6') * whip.parameters['rr']
58 | steerrate = whip.get_sim_output('u7')
59 | time = whip.simResults['t']
60 | newFig = bicycle.meijaard_figure_four(time, rollRate, steerrate, speed)
61 | newFig.savefig('../../figures/eom/meijaard2007-figure-four-linear.png', dpi=300)
62 | newFig.savefig('../../figures/eom/meijaard2007-figure-four-linear.pdf')
63 |
64 | # plot the eigenvalues vs speed
65 | settings = {'num':100,
66 | 'axes':'complex',
67 | 'pub':True,
68 | 'width':5.0}
69 | start = 0.0
70 | stop = -10.0 / moorePar['rr']
71 | rootLocus = whip.plot_root_locus('u6', start, stop, factor=('v',
72 | -moorePar['rr']), units='m/s', **settings)
73 | rootLocus.savefig('../../figures/eom/root-locus-complex.png', dpi=300)
74 | rootLocus.savefig('../../figures/eom/root-locus-complex.pdf')
75 |
76 | settings['axes'] = 'parameter'
77 | settings['ylim'] = (-10, 10)
78 | rootLocus = whip.plot_root_locus('u6', start, stop, factor=('v',
79 | -moorePar['rr']), units='m/s', **settings)
80 | ax = rootLocus.axes[0]
81 | # find the lines
82 | firstVals = np.array([line.get_ydata()[0] for line in ax.lines])
83 |
84 | casterLine = ax.lines[np.argmin(firstVals)]
85 | capsizeLine = ax.lines[np.nonzero((firstVals > casterLine.get_ydata()[0]) &
86 | (firstVals < 0))[0]]
87 | upperWeaveLine = ax.lines[np.argmax(firstVals)]
88 | lowerWeaveLine = ax.lines[np.nonzero((firstVals < upperWeaveLine.get_ydata()[0]) &
89 | (firstVals > 0))[0]]
90 |
91 | for i, val in enumerate(firstVals):
92 | if np.abs(val - 0.) < 1e-10:
93 | ax.lines[i].set_color(upperWeaveLine.get_color())
94 |
95 | lowerWeaveLine.set_color(upperWeaveLine.get_color())
96 |
97 | ax.annotate('Caster',
98 | casterLine.get_xydata()[np.argmin(np.abs(casterLine.get_xdata() -
99 | 1.0))], xytext=(2.5, -7.0), color=casterLine.get_color(),
100 | arrowprops={'arrowstyle':"->", 'color':casterLine.get_color()})
101 | ax.annotate('Capsize',
102 | capsizeLine.get_xydata()[np.argmin(np.abs(capsizeLine.get_xdata() -
103 | 7.0))], xytext=(8.25, 2.5), color=capsizeLine.get_color(),
104 | arrowprops={'arrowstyle':"->", 'color':capsizeLine.get_color()})
105 | ax.annotate('Weave',
106 | upperWeaveLine.get_xydata()[np.argmin(np.abs(upperWeaveLine.get_xdata() -
107 | 2.0))], xytext=(2.5, 6.0), color=upperWeaveLine.get_color(),
108 | arrowprops={'arrowstyle':"->", 'color':upperWeaveLine.get_color()})
109 |
110 | weaveSpeed = upperWeaveLine.get_xdata()[upperWeaveLine.get_ydata() > 0.0][-1]
111 | capsizeSpeed = capsizeLine.get_xdata()[capsizeLine.get_ydata() >= 0.0][0]
112 |
113 | ax.plot([weaveSpeed, weaveSpeed], [-11.0, 0.], 'k-')
114 | ax.plot([weaveSpeed, weaveSpeed], [-11.0, 0.], 'ko')
115 | ax.plot([capsizeSpeed, capsizeSpeed], [-11.0, 0.], 'k-')
116 | ax.plot([capsizeSpeed, capsizeSpeed], [-11.0, 0.], 'ko')
117 | ax.text(4.5, -9.0, 'Stable')
118 | ax.arrow(weaveSpeed, -7.0, capsizeSpeed - weaveSpeed, 0.0, shape='full')
119 |
120 | rootLocus.savefig('../../figures/eom/root-locus.png', dpi=300)
121 | rootLocus.savefig('../../figures/eom/root-locus.pdf')
122 |
123 | # now find the eigenvalues
124 | equilibrium[whip.stateNames.index('u6')] = -5.0 / moorePar['rr']
125 | whip.linear(equilibrium)
126 | e = whip.eig()[0]
127 | r = e.real
128 | r.sort()
129 | i = e.imag
130 | i.sort()
131 | mooreEig = np.array([r[0], r[1], r[3], i[-1]])
132 | mooreEig.sort()
133 | meijaardEig = np.array([-14.07838969279822, # caster
134 | -0.77534188219585, # weave real
135 | -0.32286642900409, # capsize
136 | 4.46486771378823]) # weave imag
137 | sigFigs = [16, 14, 14, 15]
138 |
139 | table = """.. list-table:: Linear Whipple Model Comparision
140 | :header-rows: 1
141 |
142 | * - Model
143 | - Caster
144 | - Weave Real
145 | - Capsize
146 | - Weave Imaginary\n"""
147 |
148 | for model, eigs in zip(['Meijaard', 'Moore'], [meijaardEig, mooreEig]):
149 | table += ' ' * 3 + '* - ' + model + '\n'
150 | for i, e in enumerate(eigs):
151 | sig = '%1.' + str(sigFigs[i] - 1) + 'e'
152 | table += ' ' * 5 + '- ' + sig % e + '\n'
153 |
154 | print table
155 |
156 | with open('../../tables/eom/linear-compare.rst', 'w') as f:
157 | f.write(table)
158 |
--------------------------------------------------------------------------------
/src/eom/nonlinear_simulation.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This reproduces Figure 4 in Meijaard 2007 with my model.
4 |
5 | # dependencies
6 | import numpy as np
7 | import bicycleparameters as bp
8 | from dtk import bicycle
9 |
10 | # local dependencies
11 | try:
12 | f = open('Whipple.py', 'r')
13 | except IOError:
14 | from altk import alparse
15 | alparse.alparse('Whipple', 'Whipple', code='Python')
16 | else:
17 | f.close()
18 | del f
19 |
20 | from Whipple import Whipple
21 |
22 | # create the Whipple model (with my parameters)
23 | whip = Whipple()
24 |
25 | # load the benchmark parameters
26 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
27 | benchmark = bp.Bicycle('Benchmark', pathToData)
28 | benchmarkPar = bp.io.remove_uncertainties(benchmark.parameters['Benchmark'])
29 | # convert to my parameter set
30 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
31 | # set the parameters
32 | whip.parameters = moorePar
33 |
34 | # set the initial conditions to match Meijaard2007
35 | speedNaught = 4.6
36 | rollRateNaught = 0.5
37 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'],
38 | moorePar['rr'], moorePar['d1'], moorePar['d2'], moorePar['d3'])
39 | # set up the simulation
40 | whip.initialConditions = np.array([0.0] * 8 + [rollRateNaught, -speedNaught /
41 | moorePar['rr'], 0.0])
42 | whip.initialConditions[whip.stateNames.index('q5')] = pitchAngle
43 |
44 | # integration settings
45 | ts = 0.01 # step size
46 | tf = 5. # final time
47 | whip.intOpts['ts'] = ts
48 | whip.intOpts['tf'] = tf
49 |
50 | whip.simulate()
51 |
52 | # plot figure 4 from Meijaard2007 using my model
53 | rollRate = whip.get_sim_output('u4')
54 | speed = -whip.get_sim_output('u6') * whip.parameters['rr']
55 | steerRate = whip.get_sim_output('u7')
56 | time = whip.simResults['t']
57 | newFig = bicycle.meijaard_figure_four(time, rollRate, steerRate, speed)
58 | newFig.savefig('../../figures/eom/meijaard2007-figure-four.png', dpi=300)
59 |
--------------------------------------------------------------------------------
/src/eom/plot_eigenvectors.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This script requires the ImageMagick montage command line tool.
4 |
5 | import os
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 | import bicycleparameters as bp
9 | from dtk import bicycle
10 |
11 | # create the Whipple model (with my parameters)
12 | try:
13 | f = open('Whipple.py', 'r')
14 | except IOError:
15 | from altk import alparse
16 | alparse.alparse('Whipple', 'Whipple', code='Python')
17 | else:
18 | f.close()
19 | del f
20 |
21 | # local dependencies
22 | from Whipple import LinearWhipple
23 |
24 | fig_size = [3., 3.]
25 | params = {'backend': 'ps',
26 | 'axes.labelsize': 10,
27 | 'axes.titlesize': 10,
28 | 'text.fontsize': 10,
29 | 'legend.fontsize': 8,
30 | 'xtick.labelsize': 8,
31 | 'ytick.labelsize': 8,
32 | 'text.usetex': True,
33 | 'figure.figsize': fig_size,
34 | 'figure.dpi': 300}
35 | plt.rcParams.update(params)
36 |
37 | # load the benchmark parameters
38 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
39 | benchmark = bp.Bicycle('Benchmark', pathToData)
40 | benchmarkPar = bp.io.remove_uncertainties(benchmark.parameters['Benchmark'])
41 | # convert to my parameter set
42 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
43 |
44 | whip = LinearWhipple()
45 | whip.set_parameters(moorePar)
46 |
47 | # linearize about the nominal configuration
48 | equilibrium = np.zeros(len(whip.stateNames))
49 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
50 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
51 | equilibrium[whip.stateNames.index('q5')] = pitchAngle
52 |
53 | # below the weave bifurcation
54 | speedNaught = 0.5
55 | u6Naught = -speedNaught / moorePar['rr']
56 | equilibrium[whip.stateNames.index('u6')] = u6Naught
57 | whip.linear(equilibrium)
58 |
59 | figs = whip.plot_eigenvectors(states=('q4', 'q7'))
60 |
61 | for i, f in enumerate(figs):
62 | legTexts = f.axes[0].legend_.texts
63 | for text in legTexts:
64 | text.set_text('$' + text.get_text()[0] + '_' + text.get_text()[1] + '$')
65 | speed = 'Half'
66 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.png')
67 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.pdf')
68 | figPath = '../../figures/eom/'
69 | os.system('montage -geometry +0+0 {0}eVecHalf-1.png {0}eVecHalf-2.png {0}eVecHalf-3.png {0}eVecHalf-4.png {0}eVecHalf.png'.format(figPath))
70 | #os.system('montage -geometry +0+0 {0}eVecHalf-1.pdf {0}eVecHalf-2.pdf {0}eVecHalf-3.pdf {0}eVecHalf-4.pdf {0}eVecHalf.pdf'.format(figPath))
71 |
72 | fig_size = [2., 2.]
73 | params = {'figure.figsize': fig_size}
74 | plt.rcParams.update(params)
75 |
76 | # unstable weave
77 | speedNaught = 3.0
78 | u6Naught = -speedNaught / moorePar['rr']
79 | equilibrium[whip.stateNames.index('u6')] = u6Naught
80 | whip.linear(equilibrium)
81 |
82 | figs = whip.plot_eigenvectors(states=('q4', 'q7'))
83 |
84 | for i, f in enumerate(figs):
85 | legTexts = f.axes[0].legend_.texts
86 | for text in legTexts:
87 | text.set_text('$' + text.get_text()[0] + '_' + text.get_text()[1] + '$')
88 | speed = 'Three'
89 | ax = f.axes[0]
90 | bbox = ax.get_position()
91 | ax.set_position([0.125, 0.1, 0.9 - 0.125, 0.9 - 0.2])
92 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.png')
93 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.pdf')
94 | os.system('montage -geometry +0+0 {0}eVecThree-1.png {0}eVecThree-2.png {0}eVecThree-3.png {0}eVecThree.png'.format(figPath))
95 | #os.system('montage -geometry +0+0 {0}eVecThree-1.pdf {0}eVecThree-2.pdf {0}eVecThree-3.pdf {0}eVecThree.pdf'.format(figPath))
96 |
97 | # stable
98 | speedNaught = 5.0
99 | u6Naught = -speedNaught / moorePar['rr']
100 | equilibrium[whip.stateNames.index('u6')] = u6Naught
101 | whip.linear(equilibrium)
102 |
103 | figs = whip.plot_eigenvectors(states=('q4', 'q7'))
104 |
105 | for i, f in enumerate(figs):
106 | legTexts = f.axes[0].legend_.texts
107 | for text in legTexts:
108 | text.set_text('$' + text.get_text()[0] + '_' + text.get_text()[1] + '$')
109 | speed = 'Five'
110 | ax = f.axes[0]
111 | bbox = ax.get_position()
112 | ax.set_position([0.125, 0.1, 0.9 - 0.125, 0.9 - 0.2])
113 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.png')
114 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.pdf')
115 | os.system('montage -geometry +0+0 {0}eVecFive-1.png {0}eVecFive-2.png {0}eVecFive-3.png {0}eVecFive.png'.format(figPath))
116 | #os.system('montage -geometry +0+0 {0}eVecFive-1.pdf {0}eVecFive-2.pdf {0}eVecFive-3.pdf {0}eVecFive.pdf'.format(figPath))
117 |
118 | # unstable capsize
119 | speedNaught = 7.0
120 | u6Naught = -speedNaught / moorePar['rr']
121 | equilibrium[whip.stateNames.index('u6')] = u6Naught
122 | whip.linear(equilibrium)
123 |
124 | figs = whip.plot_eigenvectors(states=('q4', 'q7'))
125 |
126 | for i, f in enumerate(figs):
127 | legTexts = f.axes[0].legend_.texts
128 | for text in legTexts:
129 | text.set_text('$' + text.get_text()[0] + '_' + text.get_text()[1] + '$')
130 | speed = 'Seven'
131 | ax = f.axes[0]
132 | bbox = ax.get_position()
133 | ax.set_position([0.125, 0.1, 0.9 - 0.125, 0.9 - 0.2])
134 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.png')
135 | f.savefig('../../figures/eom/eVec' + speed + '-' + str(i + 1) + '.pdf')
136 | os.system('montage -geometry +0+0 {0}eVecSeven-1.png {0}eVecSeven-2.png {0}eVecSeven-3.png {0}eVecSeven.png'.format(figPath))
137 | #os.system('montage -geometry +0+0 {0}eVecSeven-1.pdf {0}eVecSeven-2.pdf {0}eVecSeven-3.pdf {0}eVecSeven.pdf'.format(figPath))
138 |
--------------------------------------------------------------------------------
/src/extensions/arms/ArmsDynamics.in:
--------------------------------------------------------------------------------
1 | File: ArmsDynamics.in
2 | -----------------------------------------------------------------------------
3 | Note: Use spaces not tabs while editing this file.
4 | ----------------------------------------------------------+------------------
5 | Description Quantity Units | Value
6 | ----------------------------------------------------------|------------------
7 | Constant d1 UNITS | 0.0
8 | Constant d10 UNITS | 0.0
9 | Constant d12 UNITS | 0.0
10 | Constant d13 UNITS | 0.0
11 | Constant d2 UNITS | 0.0
12 | Constant d3 UNITS | 0.0
13 | Constant d4 UNITS | 0.0
14 | Constant d5 UNITS | 0.0
15 | Constant d6 UNITS | 0.0
16 | Constant d7 UNITS | 0.0
17 | Constant d8 UNITS | 0.0
18 | Constant d9 UNITS | 0.0
19 | Constant g UNITS | 0.0
20 | Constant ic11 UNITS | 0.0
21 | Constant ic22 UNITS | 0.0
22 | Constant ic31 UNITS | 0.0
23 | Constant ic33 UNITS | 0.0
24 | Constant id11 UNITS | 0.0
25 | Constant id22 UNITS | 0.0
26 | Constant ie11 UNITS | 0.0
27 | Constant ie22 UNITS | 0.0
28 | Constant ie31 UNITS | 0.0
29 | Constant ie33 UNITS | 0.0
30 | Constant if11 UNITS | 0.0
31 | Constant if22 UNITS | 0.0
32 | Constant ig11 UNITS | 0.0
33 | Constant ig33 UNITS | 0.0
34 | Constant ih11 UNITS | 0.0
35 | Constant ih33 UNITS | 0.0
36 | Constant ii11 UNITS | 0.0
37 | Constant ii33 UNITS | 0.0
38 | Constant ij11 UNITS | 0.0
39 | Constant ij33 UNITS | 0.0
40 | Constant l1 UNITS | 0.0
41 | Constant l2 UNITS | 0.0
42 | Constant l3 UNITS | 0.0
43 | Constant l4 UNITS | 0.0
44 | Constant l5 UNITS | 0.0
45 | Constant l6 UNITS | 0.0
46 | Constant mc UNITS | 0.0
47 | Constant md UNITS | 0.0
48 | Constant me UNITS | 0.0
49 | Constant mf UNITS | 0.0
50 | Constant mg UNITS | 0.0
51 | Constant mh UNITS | 0.0
52 | Constant mi UNITS | 0.0
53 | Constant mj UNITS | 0.0
54 | Constant rf UNITS | 0.0
55 | Constant rr UNITS | 0.0
56 |
57 | Initial Value q1 UNITS | 0.0
58 | Initial Value q10 UNITS | 0.0
59 | Initial Value q11 UNITS | 0.0
60 | Initial Value q12 UNITS | 0.0
61 | Initial Value q13 UNITS | 0.0
62 | Initial Value q14 UNITS | 0.0
63 | Initial Value q15 UNITS | 0.0
64 | Initial Value q16 UNITS | 0.0
65 | Initial Value q2 UNITS | 0.0
66 | Initial Value q3 UNITS | 0.0
67 | Initial Value q4 UNITS | 0.0
68 | Initial Value q5 UNITS | 0.0
69 | Initial Value q6 UNITS | 0.0
70 | Initial Value q7 UNITS | 0.0
71 | Initial Value q8 UNITS | 0.0
72 | Initial Value q9 UNITS | 0.0
73 | Initial Value u4 UNITS | 0.0
74 | Initial Value u6 UNITS | 0.0
75 | Initial Value u7 UNITS | 0.0
76 |
77 | Initial Time TINITIAL sec | 0.0
78 | Final Time TFINAL sec | 1.0
79 | Integration Step INTEGSTP sec | 0.1
80 | Print-Integer PRINTINT Positive Integer| 1
81 | Absolute Error ABSERR | 1.0E-08
82 | Relative Error RELERR | 1.0E-07
83 | ----------------------------------------------------------+------------------
84 |
85 | Note:
86 |
87 | INTEGSTP*PRINTINT is the time-interval for writing of output.
88 |
89 | ABSERR should be set equal to 10^(-8)*Xsmall, where Xsmall is the estimated
90 | smallest maximum absolute value of the variables being integrated.
91 |
92 | RELERR should be set equal to 10^(-d), where d is the desired number of
93 | significant digits of numerical integration results.
94 |
--------------------------------------------------------------------------------
/src/extensions/arms/arm_init.py:
--------------------------------------------------------------------------------
1 | # so Yeadon defines his shoulder angles one way and I define mine another, how
2 | # can I map his angles to my angles?
3 | import sympy as sy
4 | import sympy.physics.mechanics as me
5 |
6 | alpha, beta, gamma, delta = sy.symbols('alpha beta gamma delta')
7 | lam, q13, q14, q15 = sy.symbols('lambda q13 q14 q15')
8 |
9 | newton = me.ReferenceFrame('N', indices=('1', '2', '3'))
10 | bicycle = me.ReferenceFrame('C', indices=('1', '2', '3'))
11 | yeadon = me.ReferenceFrame('Y', indices=('1', '2', '3'))
12 | torso = me.ReferenceFrame('T', indices=('1', '2', '3'))
13 |
14 | bicycle.orient(newton, 'Axis', (lam, newton['2']))
15 | yeadon.orient(newton, 'Body', (3 * sy.pi / 2, sy.pi, 0), '312')
16 | torso.orient(yeadon, 'Axis', (delta, yeadon['1']))
17 |
18 | mooreArm = me.ReferenceFrame('I', indices=('1', '2', '3'))
19 | mooreArm.orient(bicycle, 'Body', (q13, q14, q15), '123')
20 | yeadonArm = me.ReferenceFrame('A', indices=('1', '2', '3'))
21 | yeadonArm.orient(torso, 'Body', (alpha, beta, gamma), '213')
22 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms.py:
--------------------------------------------------------------------------------
1 | from numpy.linalg import norm
2 | from math import sin, cos
3 | from scipy import io
4 | import bicycleparameters as bp
5 | from dtk import bicycle
6 |
7 | pathToParameters = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data'
8 |
9 | rider = 'Luke'
10 |
11 | if rider == 'Jason':
12 | bikeName = 'Rigid'
13 | elif rider == 'Charlie' or rider == 'Luke':
14 | bikeName = 'Rigidcl'
15 |
16 | # load the rigid bicycle and seat Luke
17 | rigidWithRider = bp.Bicycle(bikeName, forceRawCalc=True, pathToData=pathToParameters)
18 | rigidWithRider.add_rider(rider, reCalc=True)
19 | h = rigidWithRider.human
20 |
21 | # find the inertia of the humans without arms, this is with respect to the
22 | # benchmark coordinate system and about the CoM of the subset of parts
23 | humanMass, humanCoM, humanInertia = h.combine_inertia(('P', 'T', 'C', 'K1', 'K2', 'J1', 'J2'))
24 | riderPar = {'IBxx': humanInertia[0, 0],
25 | 'IByy': humanInertia[1, 1],
26 | 'IBzz': humanInertia[2, 2],
27 | 'IBxz': humanInertia[2, 0],
28 | 'mB': humanMass,
29 | 'xB': humanCoM[0][0],
30 | 'yB': humanCoM[1][0],
31 | 'zB': humanCoM[2][0]}
32 |
33 | rigid = bp.Bicycle(bikeName, forceRawCalc=True, pathToData=pathToParameters)
34 | benchmark = bp.rider.combine_bike_rider(rigid.parameters['Benchmark'], riderPar)
35 | benchmark = bp.io.remove_uncertainties(benchmark)
36 | par = bicycle.benchmark_to_moore(benchmark)
37 |
38 | # get the arm parameters
39 | # note that the right and left arms have the same parameters due to symmetry, I
40 | # should probably reduce the number of parameters in the analytical model to
41 | # reflect this
42 |
43 | # right upper arm, G
44 | par['mg'] = h.B1.Mass
45 | par['l5'] = h.B1.relCOM[2][0]
46 | IG = h.B1.relInertia
47 | par['ig11'] = IG[0, 0]
48 | par['ig33'] = IG[2, 2]
49 | par['d12'] = h.B1.length
50 |
51 | # shoulders
52 | par['d6'] = (h.B1.pos[0, 0] * cos(benchmark['lam']) - h.B1.pos[2, 0] * sin(benchmark['lam']) -
53 | benchmark['rR'] * sin(benchmark['lam']))
54 | par['d7'] = abs(h.B1.pos[1, 0])
55 | par['d8'] = (h.B1.pos[0, 0] * sin(benchmark['lam']) + h.B1.pos[2, 0] * cos(benchmark['lam']) +
56 | benchmark['rR'] * cos(benchmark['lam']))
57 |
58 | # right lower arm, H
59 | par['mh'] = h.B2.Mass
60 | par['l6'] = h.B2.relCOM[2][0]
61 | IH = h.B2.relInertia
62 | par['ih11'] = IH[0, 0]
63 | par['ih33'] = IH[2, 2]
64 | knuckle = h.b[6].pos
65 | par['d13'] = norm(knuckle - h.B2.pos)
66 |
67 | # grips
68 | par['d11'] = ((knuckle[2, 0] + benchmark['rF']) * cos(benchmark['lam']) +
69 | (knuckle[0, 0] - benchmark['w'])
70 | * sin(benchmark['lam']))
71 | par['d9'] = ((knuckle[0, 0] - benchmark['w'] - par['d11'] * sin(benchmark['lam'])) /
72 | cos(benchmark['lam']))
73 | par['d10'] = abs(knuckle[1, 0])
74 |
75 | # left upper arm, I
76 | par['mi'] = h.A1.Mass
77 | II = h.A1.relInertia
78 | par['ii11'] = II[0, 0]
79 | par['ii33'] = II[2, 2]
80 |
81 | # left lower arm, J
82 | par['mj'] = h.A2.Mass
83 | IJ = h.A2.relInertia
84 | par['ij11'] = IJ[0, 0]
85 | par['ij33'] = IJ[2, 2]
86 |
87 | # lateral force point
88 | par['d4'] = (benchmark['xcl'] * cos(benchmark['lam']) - benchmark['zcl'] * sin(benchmark['lam']) -
89 | benchmark['rR'] * sin(benchmark['lam']))
90 | par['d5'] = (benchmark['xcl'] * sin(benchmark['lam']) + benchmark['zcl'] * cos(benchmark['lam']) +
91 | benchmark['rR'] * cos(benchmark['lam']))
92 |
93 | io.savemat('../../../data/extensions/armspar-' + rider + '.mat', par)
94 |
95 | # now get some guesses for the arm angles at the nominal configuration
96 | # these guesses are not correct, as the mapping from yeadon's coordinates to
97 | # mine are not trivial
98 |
99 | q = {}
100 |
101 | q['q5'] = benchmark['lam']
102 |
103 | # right arm
104 | q['q9'] = -h.CFG['CB1abduction']
105 | q['q10'] = h.CFG['CB1elevation']
106 | q['q11'] = h.CFG['CB1rotation']
107 | q['q12'] = -h.CFG['B1B2flexion']
108 |
109 | # left arm
110 | q['q13'] = -h.CFG['CA1abduction']
111 | q['q14'] = h.CFG['CA1elevation']
112 | q['q15'] = h.CFG['CA1rotation']
113 | q['q16'] = -h.CFG['A1A2flexion']
114 |
115 | io.savemat('../../../data/extensions/armsinit-' + rider + '.mat', q)
116 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_holonomic.m:
--------------------------------------------------------------------------------
1 | function h = arms_holonomic(qd, qi, p)
2 | % function h = arms_holonomic(qd, qi, p)
3 | %
4 | % Returns the value of the holonomic constraints for the Whipple model with
5 | % rider arms given the configuration and parameters.
6 | %
7 | % Parameters
8 | % ----------
9 | % qd : double, size(8, 1)
10 | % The vector of dependent configuration variables.
11 | % q9, q10, q11, q12, q13, q14, q15, q16
12 | % q : double, size(11, 1)
13 | % The vector of independent configuration variables.
14 | % q4, q5, q7
15 | % p : structure
16 | % The geometric parameters of the model.
17 | %
18 | % Returns
19 | % -------
20 | % h : double, size(9, 1)
21 | % The value of the holonomic constraints.
22 |
23 | q4 = qi(1); % roll angle
24 | q5 = qi(2); % pitch angle
25 | q7 = qi(3); % steer angle
26 |
27 | q9 = qd(1);
28 | q10 = qd(2);
29 | q11 = qd(3);
30 | q12 = qd(4);
31 | q13 = qd(5);
32 | q14 = qd(6);
33 | q15 = qd(7);
34 | q16 = qd(8);
35 |
36 | z = zeros(88,1);
37 |
38 | z(3) = cos(q4);
39 | z(4) = sin(q4);
40 | z(5) = cos(q5);
41 | z(6) = sin(q5);
42 | z(9) = cos(q7);
43 | z(10) = sin(q7);
44 | z(13) = cos(q10);
45 | z(14) = cos(q11);
46 | z(15) = z(13)*z(14);
47 | z(16) = sin(q11);
48 | z(17) = z(13)*z(16);
49 | z(18) = sin(q10);
50 | z(19) = cos(q9);
51 | z(20) = sin(q9);
52 | z(21) = z(16)*z(19) + z(14)*z(18)*z(20);
53 | z(23) = z(13)*z(20);
54 | z(24) = z(16)*z(20) - z(14)*z(18)*z(19);
55 | z(25) = z(14)*z(20) + z(16)*z(18)*z(19);
56 | z(26) = z(13)*z(19);
57 | z(27) = cos(q12);
58 | z(28) = sin(q12);
59 | z(29) = cos(q14);
60 | z(30) = cos(q15);
61 | z(31) = z(29)*z(30);
62 | z(32) = sin(q15);
63 | z(33) = z(29)*z(32);
64 | z(34) = sin(q14);
65 | z(35) = cos(q13);
66 | z(36) = sin(q13);
67 | z(37) = z(32)*z(35) + z(30)*z(34)*z(36);
68 | z(39) = z(29)*z(36);
69 | z(40) = z(32)*z(36) - z(30)*z(34)*z(35);
70 | z(41) = z(30)*z(36) + z(32)*z(34)*z(35);
71 | z(42) = z(29)*z(35);
72 | z(43) = cos(q16);
73 | z(44) = sin(q16);
74 | z(45) = z(5)*z(9);
75 | z(46) = z(6)*z(9);
76 | z(47) = z(5)*z(10);
77 | z(48) = z(6)*z(10);
78 | z(49) = z(3)*z(10) + z(4)*z(46);
79 | z(50) = z(4)*z(10) - z(3)*z(46);
80 | z(51) = z(3)*z(9) - z(4)*z(48);
81 | z(53) = z(4)*z(5);
82 | z(54) = z(3)*z(5);
83 | z(55) = z(47)*z(53) - z(6)*z(51);
84 | z(56) = z(45)*z(51) + z(47)*z(49);
85 | z(57) = z(55)^2 + z(56)^2;
86 | z(58) = z(57)^0.5;
87 | z(59) = z(55)/z(58);
88 | z(60) = z(56)/z(58);
89 | z(61) = p.rf*z(59);
90 | z(62) = p.rf*z(60);
91 | z(64) = z(3)*z(6);
92 | z(68) = z(15)*z(28) + z(18)*z(27);
93 | z(69) = z(21)*z(28) - z(23)*z(27);
94 | z(70) = z(24)*z(28) + z(26)*z(27);
95 | z(74) = z(31)*z(44) + z(34)*z(43);
96 | z(75) = z(37)*z(44) - z(39)*z(43);
97 | z(76) = z(40)*z(44) + z(42)*z(43);
98 | z(80) = z(5)*z(25) + z(6)*z(17);
99 | z(86) = z(5)*z(41) + z(6)*z(33);
100 |
101 | h = zeros(9, 1);
102 |
103 | % this is the roll, pitch, steer constraint
104 | h(1) = p.d2*z(54) + z(54)*z(62) + z(50)*(p.d3+z(61)) - p.d1*z(64) - p.rr*z(3);
105 | % these set of equations are the constraints which force the hands on the
106 | % grips and the arms to hang downward
107 | h(2) = p.d6 + p.d10*z(10) + p.d12*z(18) + p.d13*z(68) - p.d1 - (p.d3+p.d9)*z(9);
108 | h(3) = p.d7 + p.d13*z(69) - p.d10*z(9) - p.d12*z(23) - (p.d3+p.d9)*z(10);
109 | h(4) = p.d8 + p.d12*z(26) + p.d13*z(70) - p.d11 - p.d2;
110 | h(5) = p.d6 + p.d12*z(34) + p.d13*z(74) - p.d1 - p.d10*z(10) - (p.d3+p.d9)*z(9);
111 | h(6) = p.d10*z(9) + p.d13*z(75) - p.d7 - p.d12*z(39) - (p.d3+p.d9)*z(10);
112 | h(7) = p.d8 + p.d12*z(42) + p.d13*z(76) - p.d11 - p.d2;
113 | h(8) = z(80);
114 | h(9) = z(86);
115 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_integrate.m:
--------------------------------------------------------------------------------
1 | p = load('armspar.mat');
2 | q = load('armsinit.mat');
3 |
4 | % we are looking at the nominal configuration
5 | q4 = 0;
6 | q7 = 0;
7 |
8 | % solve for the pitch angle
9 | qi = [q4, q7];
10 | pitch = @(qd)pitch_constraint(qd, qi, p);
11 | q5 = fsolve(pitch, q.q5)
12 |
13 | q9g = -0.0257;
14 | q10g = -0.2245;
15 | q11g = 0.2572;
16 | q12g = 1.288;
17 | holoGuess = [q9g, q10g, q11g, q12g, -q9g, q10g, -q11g, q12g];
18 | % plot the initial guess
19 | points = arms_points([0, 0, 0, q4, q5, 0, q7, 0, holoGuess], p);
20 | plot_arms(points)
21 |
22 | % solve for the exact solution
23 | qi = [q4, q5, q7];
24 | constraint = @(qd)arms_holonomic(qd, qi, p);
25 | holoInit = fsolve(constraint, holoGuess);
26 | % plot the solution
27 | points = arms_points([0, 0, 0, q4, q5, 0, q7, 0, holoInit], p);
28 | plot_arms(points)
29 |
30 | qi = [q5, q7];
31 | right = @(qd)right_arm_constraint(qd, qi, p);
32 | [qRight, rightVal] = fsolve(right, [q9g, q10g, q11g, q12g]);
33 |
34 | left = @(qd)left_arm_constraint(qd, qi, p);
35 | [qLeft, leftVal] = fsolve(left, [-q9g, q10g, -q11g, q12g]);
36 |
37 | points = arms_points([0, 0, 0, q4, q5, 0, q7, 0, qRight, qLeft], p);
38 | plot_arms(points)
39 |
40 | x0 = [0, 0, 0, q4, q5, q7, 0, 0, qRight, qLeft, 0.5, -7.0 / p.rr, 0];
41 |
42 | odefunc = @(t, x)arms_ode(t, x, p);
43 |
44 | [time, states] = ode45(odefunc, [0 2], x0);
45 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_linear.m:
--------------------------------------------------------------------------------
1 | function arms_linear(rider)
2 | % function arms_linear(rider)
3 | %
4 | % Computes the state matrices for a set of speeds.
5 | %
6 | % Parameters
7 | % ----------
8 | % rider : char
9 | % Either 'Charlie', 'Jason', or 'Luke'.
10 |
11 | p = load(['../../../data/extensions/' 'armspar-' rider '.mat']);
12 | q = load(['../../../data/extensions/' 'armsinit-' rider '.mat']);
13 |
14 | % we are looking at the nominal configuration
15 | q4 = 0;
16 | q7 = 0;
17 |
18 | % solve for the pitch angle
19 | qi = [q4, q7];
20 | pitch = @(qd)pitch_constraint(qd, qi, p);
21 | q5 = fsolve(pitch, q.q5, optimset('Display', 'Off'));
22 |
23 | % initial guesses for the arm angles
24 | q9g = -0.0257;
25 | q10g = -0.2245;
26 | q11g = 0.2572;
27 | q12g = 1.288;
28 |
29 | % solve the arm constraints
30 | qi = [q5, q7];
31 | right = @(qd)right_arm_constraint(qd, qi, p);
32 | qRight = fsolve(right, [q9g, q10g, q11g, q12g], optimset('Display', 'Off'));
33 |
34 | left = @(qd)left_arm_constraint(qd, qi, p);
35 | qLeft = fsolve(left, [-q9g, q10g, -q11g, q12g], optimset('Display', 'Off'));
36 |
37 | % caluculate eigenvalues for a range of speeds
38 | speed = 0:0.1:10;
39 | w = zeros(4, length(speed));
40 | stateMatrices = zeros(length(speed), 4, 4);
41 | inputMatrices = zeros(length(speed), 4, 3);
42 | for k = 1:length(speed)
43 | eq = [0, 0, 0, q4, q5, 0, q7, 0, qRight, qLeft, 0, -speed(k) / p.rr, 0];
44 | [A, B] = arms_state_space(eq, p);
45 | [A, B] = arms_reduce_system(A, B);
46 | stateMatrices(k, :, :) = A;
47 | inputMatrices(k, :, :) = B;
48 | w(:, k) = eig(A);
49 | end
50 |
51 | save(['../../../data/extensions/' 'armsAB-' rider '.mat'], 'speed', 'stateMatrices', 'inputMatrices')
52 |
53 | plot(speed, real(w), '.b')
54 | hold on
55 | plot(speed, imag(w), '.r')
56 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_points.m:
--------------------------------------------------------------------------------
1 | function points = arms_points(q, p)
2 | % function points = arms_points(q, p)
3 | %
4 | % Returns the coordinates for various points in the Whipple model with arms.
5 | %
6 | % Parameters
7 | % ----------
8 | % q : double, size(16, 1)
9 | % All of the configuration variables in order from 1 to 16.
10 |
11 | % define the direction cosine matrices for each of the bodies
12 | % A vector, a, can be expressed in the N frame, n, by pre-multiplying by n_a.
13 | % n = n_a * a
14 | n_a = [cos(q(3)) -sin(q(3)) 0
15 | sin(q(3)), cos(q(3)), 0
16 | 0, 0, 1,];
17 |
18 | a_b= [1, 0, 0
19 | 0, cos(q(4)), -sin(q(4))
20 | 0, sin(q(4)), cos(q(4))];
21 |
22 | b_c = [cos(q(5)), 0, sin(q(5))
23 | 0, 1, 0
24 | -sin(q(5)), 0, cos(q(5))];
25 |
26 | c_d = [cos(q(6)), 0, sin(q(6))
27 | 0, 1, 0
28 | -sin(q(6)), 0, cos(q(6))];
29 |
30 | c_e = [cos(q(7)), -sin(q(7)), 0
31 | sin(q(7)), cos(q(7)), 0
32 | 0, 0, 1];
33 |
34 | e_f = [cos(q(8)), 0, sin(q(8))
35 | 0, 1, 0
36 | -sin(q(8)), 0, cos(q(8))];
37 |
38 | c_g = [cos(q(10))*cos(q(11)), -sin(q(11))*cos(q(10)), sin(q(10))
39 | sin(q(11))*cos(q(9)) + sin(q(10))*sin(q(9))*cos(q(11)), cos(q(11))*cos(q(9)) - sin(q(10))*sin(q(11))*sin(q(9)), -sin(q(9))*cos(q(10))
40 | sin(q(11))*sin(q(9)) - sin(q(10))*cos(q(11))*cos(q(9)), sin(q(9))*cos(q(11)) + sin(q(10))*sin(q(11))*cos(q(9)), cos(q(10))*cos(q(9))];
41 |
42 | g_h = [cos(q(12)), 0, sin(q(12))
43 | 0, 1, 0
44 | -sin(q(12)), 0, cos(q(12))];
45 |
46 | c_i = [cos(q(14))*cos(q(15)), -sin(q(15))*cos(q(14)), sin(q(14))
47 | sin(q(15))*cos(q(13)) + sin(q(13))*sin(q(14))*cos(q(15)), cos(q(13))*cos(q(15)) - sin(q(13))*sin(q(14))*sin(q(15)), -sin(q(13))*cos(q(14))
48 | sin(q(13))*sin(q(15)) - sin(q(14))*cos(q(13))*cos(q(15)), sin(q(13))*cos(q(15)) + sin(q(14))*sin(q(15))*cos(q(13)), cos(q(13))*cos(q(14))];
49 |
50 | i_j = [cos(q(16)), 0, sin(q(16))
51 | 0, 1, 0
52 | -sin(q(16)), 0, cos(q(16))];
53 |
54 | %% create unit vectors for each reference frame
55 |
56 | % to express a vector in frame A in frame N: aInN * v
57 | aInN = n_a;
58 | bInN = n_a * a_b;
59 | cInN = n_a * a_b * b_c;
60 | dInN = n_a * a_b * b_c * c_d;
61 | eInN = n_a * a_b * b_c * c_e;
62 | fInN = n_a * a_b * b_c * c_e;
63 | gInN = n_a * a_b * b_c * c_g;
64 | iInN = n_a * a_b * b_c * c_i;
65 | hInN = n_a * a_b * b_c * c_g * g_h;
66 | jInN = n_a * a_b * b_c * c_i * i_j;
67 |
68 | % create the unit vectors
69 | % for example d is the d set of dextral vectors in N
70 | n = eye(3);
71 | a = aInN * n;
72 | b = bInN * n;
73 | c = cInN * n;
74 | d = dInN * n;
75 | e = eInN * n;
76 | f = fInN * n;
77 | g = gInN * n;
78 | h = hInN * n;
79 | i = iInN * n;
80 | j = jInN * n;
81 |
82 | %% calculate the vector to each point
83 | % newtonian origin to rear wheel center
84 | p_no_do = q(1) * n(:, 1) + q(2) * n(:, 2) - p.rr * b(:, 3);
85 |
86 | % rear wheel center to steer axis point
87 | p_do_ce = p.d1 * c(:, 1);
88 |
89 | % steer axis point to the front wheel center
90 | p_ce_ex = p.d2 * c(:, 3);
91 | p_ex_fo = p.d3 * e(:, 1);
92 |
93 | % right arm
94 | % locate the shoulders
95 | p_do_sr = p.d6 * c(:, 1) + p.d7 * c(:, 2) + p.d8 * c(:, 3);
96 | % locate the elbows
97 | p_sr_er = p.d12 * g(:, 3);
98 | % locate the hands
99 | p_er_hr = p.d13 * h(:, 3);
100 |
101 | % left arm
102 | % locate the shoulders
103 | p_do_sl = p.d6 * c(:, 1) - p.d7 * c(:, 2) + p.d8 * c(:, 3);
104 | % locate the elbows
105 | p_sl_el = p.d12 * i(:, 3);
106 | % locate the hands
107 | p_el_hl = p.d13 * j(:, 3);
108 |
109 | % rear wheel center to bicycle frame center
110 | p_do_co = p.l1 * c(:, 1) + p.l2 * c(:, 3);
111 |
112 | % locate the centers of mass of the arms
113 | p_sr_go = p.l5 * g(:, 3);
114 | p_sl_io = p.l5 * i(:, 3);
115 |
116 | p_er_ho = p.l6 * h(:, 3);
117 | p_el_jo = p.l6 * j(:, 3);
118 |
119 | % front wheel center to fork/handlebar center
120 | p_fo_eo = p.l3 * e(:, 1) + p.l4 * e(:, 3);
121 |
122 | % locate the grips
123 | p_fo_gr = p.d9 * e(:, 1) + p.d10 * e(:, 2) + p.d11 * e(:, 3);
124 | p_fo_gl = p.d9 * e(:, 1) - p.d10 * e(:, 2) + p.d11 * e(:, 3);
125 |
126 | % locate the lateral force point
127 | p_do_cl = p.d4 * c(:, 1) + p.d5 * c(:, 3);
128 |
129 | % locate the ground contact points
130 | p_do_dn = p.rr * b(:, 3);
131 | v = cross(cross(e(:, 2), a(:, 3)), e(:, 2));
132 | p_fo_fn = p.rf * v ./ norm(v);
133 |
134 | points = zeros(3, 20);
135 |
136 | points(:, 1) = p_no_do + p_do_dn;
137 | points(:, 2) = [nan;, nan; nan];
138 | % these make up the bicycle frame
139 | points(:, 3) = p_no_do;
140 | points(:, 4) = p_no_do + p_do_ce;
141 | points(:, 5) = p_no_do + p_do_ce + p_ce_ex;
142 | points(:, 6) = p_no_do + p_do_ce + p_ce_ex + p_ex_fo;
143 | points(:, 7) = [nan;, nan; nan];
144 | points(:, 8) = p_no_do + p_do_ce + p_ce_ex + p_ex_fo + p_fo_fn;
145 | points(:, 9) = [nan;, nan; nan];
146 | % the right arm
147 | points(:, 10) = p_no_do + p_do_sr; % right shoulder
148 | points(:, 11) = p_no_do + p_do_sr + p_sr_er; % right elbow
149 | points(:, 12) = p_no_do + p_do_sr + p_sr_er + p_er_hr;
150 | points(:, 13) = [nan;, nan; nan];
151 | % left arm
152 | points(:, 14) = p_no_do + p_do_sl; % left shoulder
153 | points(:, 15) = p_no_do + p_do_sl + p_sl_el; % left elbow
154 | points(:, 16) = p_no_do + p_do_sl + p_sl_el + p_el_hl;
155 | points(:, 17) = [nan;, nan; nan];
156 | points(:, 18) = p_no_do + p_do_ce + p_ce_ex + p_ex_fo + p_fo_gr; % right grip
157 | points(:, 19) = [nan;, nan; nan];
158 | points(:, 20) = p_no_do + p_do_ce + p_ce_ex + p_ex_fo + p_fo_gl; % left grip
159 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_reduce_system.m:
--------------------------------------------------------------------------------
1 | function [A, B] = arms_reduce_system(A, B)
2 | % function [A, B] = arms_reduce_system(A, B)
3 | %
4 | % Returns the reduced state and input matrix for the arm model by correctly
5 | % accounting for the dependent coordinates.
6 | %
7 | % Parameters
8 | % ----------
9 | % A : double, size(19, 19)
10 | % The state matrix. The states are [q1, q2, q3, q4, q5, q6, q7, q8, q9,
11 | % q10, q11, q12, q13, q14, q15, q16, u4, u6, u7]'
12 | % B : double, size(19, 4)
13 | % The input matrix. The inputs are [T4, T6, T7, Fcl]'
14 | %
15 | % Returns
16 | % -------
17 | % A : double, size(4, 4)
18 | % The state matrix. The states are [q4, q7, u4, u7]'
19 | % B : double, size(4, 3)
20 | % The input matrix. The inputs are [T4, T7, Fcl]'
21 |
22 | % remove the rows and columns that can be removed
23 | A([1:3, 5:6, 8, 18], :) = [];
24 | A(:, [1:3, 5:6, 8, 18]) = [];
25 | % this is a 12 x 12
26 | Ar = A([1:2, 11:12], [1:2, 11:12]);
27 | Ar(3, 2) = Ar(3, 2) + dot(A(11, 3:10)', A(3:10, 12));
28 | Ar(4, 2) = Ar(4, 2) + dot(A(12, 3:10)', A(3:10, 12));
29 | A = Ar;
30 |
31 | B([1:3, 5:6, 8:16, 18], :) = [];
32 | B(:, 2) = [];
33 |
--------------------------------------------------------------------------------
/src/extensions/arms/arms_state_space.m:
--------------------------------------------------------------------------------
1 | function [A, B] = arms_state_space(eq, p)
2 | % function [A, B] = arms_state_space(eq, p)
3 | %
4 | % Returns the state and input matrix for the model with respect to the
5 | % supplied equilibrium point and parameters.
6 | %
7 | % Parameters
8 | % ----------
9 | % eq : double, size(19, 1)
10 | % The desired equilibrium point.
11 | % p : structure
12 | % The model parameters.
13 | %
14 | % Returns
15 | % -------
16 | % A : double, size(19, 19)
17 | % The state matrix. The states are [q1, q2, q3, q4, q5, q6, q7, q8, q9,
18 | % q10, q11, q12, q13, q14, q15, q16, u4, u6, u7]'
19 | % B : double, size(19, 4)
20 | % The input matrix. The inputs are [T4, T6, T7, Fcl]'
21 | %
22 | % Notes
23 | % -----
24 | % The system is fourth order but you can't just delete the rows and columns
25 | % to reduce the system. Use the reduce_arms function to do this properly.
26 |
27 | delta = 1e-11; % perturbance value
28 |
29 | % make sure the constraint equations are properly solved
30 | eq = constraints(eq, p);
31 |
32 | % build the stability matrix by numerically calculating the partial
33 | % derivatives of each differential equation with respect to each state
34 | % variable
35 | A = zeros(length(eq));
36 | for j = 1:length(eq);
37 | perturb1 = eq; %initialize function input
38 | perturb2 = eq; %initialize function input
39 | % perturb the jth variable
40 | perturb1(j) = perturb1(j) + delta;
41 | perturb2(j) = perturb2(j) - delta;
42 | % solve differential equations for perturbed state, making sure the
43 | % constraints are satisfied
44 | prime1 = arms_ode(0.0, constraints(perturb1, p), p);
45 | prime2 = arms_ode(0.0, constraints(perturb2, p), p);
46 | % compute partial derivative
47 | A(:, j) = (prime1 - prime2) ./ 2 ./ delta;
48 | end
49 |
50 | inputEq = [0; 0; 0; 0];
51 | B = zeros(length(eq), length(inputEq));
52 | for j = 1:length(inputEq)
53 | % initialize function input
54 | perturb1 = inputEq;
55 | perturb2 = inputEq;
56 | % perturb the jth variable
57 | perturb1(j) = perturb1(j) + delta;
58 | perturb2(j) = perturb2(j) - delta;
59 | % solve differential equations for perturbed state, making sure the
60 | % constraints are satisfied
61 | prime1 = arms_ode(0.0, eq, p, perturb1);
62 | prime2 = arms_ode(0.0, eq, p, perturb2);
63 | % compute partial derivative
64 | B(:, j) = (prime1 - prime2) ./ 2 ./ delta;
65 | end
66 |
67 | function eq = constraints(eq, p)
68 |
69 | % solve for the pitch angle
70 | qi = [eq(4), eq(7)];
71 | pitch = @(qd)pitch_constraint(qd, qi, p);
72 | eq(5) = fsolve(pitch, eq(5), optimset('Display', 'Off'));
73 |
74 | % solve the arm constraints
75 | qi = [eq(5), eq(7)];
76 |
77 | right = @(qd)right_arm_constraint(qd, qi, p);
78 | eq(9:12) = fsolve(right, eq(9:12), optimset('Display', 'Off'));
79 |
80 | left = @(qd)left_arm_constraint(qd, qi, p);
81 | eq(13:16) = fsolve(left, eq(13:16), optimset('Display', 'Off'));
82 |
--------------------------------------------------------------------------------
/src/extensions/arms/left_arm_constraint.m:
--------------------------------------------------------------------------------
1 | function h = left_arm_constraint(qd, qi, p)
2 | % function h = left_arm_constraint(qd, qi, p)
3 | %
4 | % Returns the value of the holonomic constraints for the left arm given the
5 | % configuration and parameters.
6 | %
7 | % Parameters
8 | % ----------
9 | % qd : double, size(4, 1)
10 | % The vector of configuration variables.
11 | % q13, q14, q15, q16
12 | % qi : double, size(2, 1)
13 | % q5, q7
14 | % p : structure
15 | % The geometric parameters of the model.
16 | %
17 | % Returns
18 | % -------
19 | % h : double, size(4, 1)
20 | % The value of the holonomic constraints.
21 |
22 | q5 = qi(1);
23 | q7 = qi(2);
24 | q13 = qd(1);
25 | q14 = qd(2);
26 | q15 = qd(3);
27 | q16 = qd(4);
28 |
29 | h = zeros(4, 1);
30 |
31 | h(1) = p.d6 + p.d12 * sin(q14) + p.d13 * (sin(q14) * cos(q16)+sin(q16) * ...
32 | cos(q14) * cos(q15)) - p.d1 - p.d10 * sin(q7) - (p.d3+p.d9) * cos(q7);
33 |
34 | h(2) = p.d10 * cos(q7) - p.d7 - (p.d3+p.d9) * sin(q7) - p.d12 * sin(q13) * ...
35 | cos(q14) - p.d13 * (sin(q13) * cos(q14) * cos(q16)-sin(q16) * ...
36 | (sin(q15) * cos(q13)+sin(q13) * sin(q14) * cos(q15)));
37 |
38 | h(3) = p.d8 + p.d12 * cos(q13) * cos(q14) + p.d13 * (cos(q13) * cos(q14) * ...
39 | cos(q16)+sin(q16) * (sin(q13) * sin(q15)-sin(q14) * cos(q13) * ...
40 | cos(q15))) - p.d11 - p.d2;
41 |
42 | h(4) = sin(q15) * sin(q5) * cos(q14) + cos(q5) * (sin(q13) * cos(q15) + ...
43 | sin(q14) * sin(q15) * cos(q13));
44 |
--------------------------------------------------------------------------------
/src/extensions/arms/pitch_constraint.m:
--------------------------------------------------------------------------------
1 | function h = pitch_constraint(qd, qi, p)
2 | % function h = pitch_constraint(qd, qi, p)
3 | %
4 | % Paramters
5 | % ---------
6 | % qd : double
7 | % The dependent coordinate, pitch angle.
8 | % qi : double, size(2, 1)
9 | % The independent cooridnates, roll angle and steer angle.
10 | % p : structure
11 | % The parameters, must include rf, rr, d1, 2, d3.
12 | %
13 | % Returns
14 | % -------
15 | % h : double
16 | % The constraint evaluated at the given points.
17 |
18 | q5 = qd;
19 | q4 = qi(1);
20 | q7 = qi(2);
21 |
22 | h = p.d2 * cos(q4) * cos(q5) + p.rf * cos(q4)^2 * cos(q5)^2 / (cos(q4)^2 * ...
23 | cos(q5)^2+(sin(q4) * sin(q7)-sin(q5) * cos(q4) * cos(q7))^2)^0.5 + ...
24 | (sin(q4) * sin(q7)-sin(q5) * cos(q4) * cos(q7)) * (p.d3 + p.rf * ...
25 | (sin(q4) * sin(q7)-sin(q5) * cos(q4) * cos(q7))/(cos(q4)^2 * ...
26 | cos(q5)^2+(sin(q4) * sin(q7) - sin(q5) * cos(q4) * cos(q7))^2)^0.5) - ...
27 | p.rr * cos(q4) - p.d1 * sin(q5) * cos(q4);
28 |
--------------------------------------------------------------------------------
/src/extensions/arms/plot_arms.m:
--------------------------------------------------------------------------------
1 | function plot_arms(points)
2 | figure()
3 | lines = plot3(points(1, :), points(2, :), -points(3, :));
4 | set(lines, 'Marker', 'o', ...
5 | 'MarkerSize', 4, ...
6 | 'Linewidth', 2)
7 | hold on
8 | circle_xz(points(:, 3), norm(points(:, 3) - points(:, 1)))
9 | circle_xz(points(:, 6), norm(points(:, 6) - points(:, 8)))
10 | axis equal
11 |
12 | function circle_xz(loc, r)
13 | % function circle_xz(loc, r)
14 |
15 | theta = [0 : 0.2 : 2 * pi, 0];
16 | xp = r * cos(theta);
17 | yp = zeros(length(xp));
18 | zp = r * sin(theta);
19 |
20 | plot3(loc(1) + xp , loc(2) + yp, -(loc(3) + zp));
21 |
--------------------------------------------------------------------------------
/src/extensions/arms/right_arm_constraint.m:
--------------------------------------------------------------------------------
1 | function h = right_arm_constraint(qd, qi, p)
2 | % function h = right_arm_constraint(qd, qi, p)
3 | %
4 | % Returns the value of the holonomic constraints for the right arm given the
5 | % configuration and parameters.
6 | %
7 | % Parameters
8 | % ----------
9 | % qd : double, size(4, 1)
10 | % The vector of configuration variables.
11 | % q9, q10, q11, q12
12 | % qi : double, size(2, 1)
13 | % q5, q7
14 | % p : structure
15 | % The geometric parameters of the model.
16 | %
17 | % Returns
18 | % -------
19 | % h : double, size(4, 1)
20 | % The value of the holonomic constraints.
21 |
22 | q5 = qi(1);
23 | q7 = qi(2);
24 | q9 = qd(1);
25 | q10 = qd(2);
26 | q11 = qd(3);
27 | q12 = qd(4);
28 |
29 | h = zeros(4, 1);
30 |
31 | h(1) = p.d6 + p.d10 * sin(q7) + p.d12 * sin(q10) + p.d13 * (sin(q10) * ...
32 | cos(q12)+sin(q12) * cos(q10) * cos(q11)) - p.d1 - (p.d3 + p.d9) * cos(q7);
33 |
34 | h(2) = p.d7 - p.d10 * cos(q7) - (p.d3+p.d9) * sin(q7) - p.d12 * sin(q9) * ...
35 | cos(q10) - p.d13 * (sin(q9) * cos(q10) * cos(q12) - sin(q12) * ...
36 | (sin(q11) * cos(q9)+sin(q10) * sin(q9) * cos(q11)));
37 |
38 | h(3) = p.d8 + p.d12 * cos(q10) * cos(q9) + p.d13 * (cos(q10) * cos(q12) * ...
39 | cos(q9)+sin(q12) * (sin(q11) * sin(q9)-sin(q10) * cos(q11) * ...
40 | cos(q9))) - p.d11 - p.d2;
41 |
42 | h(4) = sin(q11) * sin(q5) * cos(q10) + cos(q5) * (sin(q9) * cos(q11) + ...
43 | sin(q10) * sin(q11) * cos(q9));
44 |
--------------------------------------------------------------------------------
/src/extensions/gyro/GyroBikeDynamics.in:
--------------------------------------------------------------------------------
1 | File: GyroBikeDynamics.in
2 | -----------------------------------------------------------------------------
3 | Note: Use spaces not tabs while editing this file.
4 | ----------------------------------------------------------+------------------
5 | Description Quantity Units | Value
6 | ----------------------------------------------------------|------------------
7 | Constant d1 UNITS | 0.0
8 | Constant d2 UNITS | 0.0
9 | Constant d3 UNITS | 0.0
10 | Constant g UNITS | 0.0
11 | Constant ic11 UNITS | 0.0
12 | Constant ic22 UNITS | 0.0
13 | Constant ic31 UNITS | 0.0
14 | Constant ic33 UNITS | 0.0
15 | Constant id11 UNITS | 0.0
16 | Constant id22 UNITS | 0.0
17 | Constant ie11 UNITS | 0.0
18 | Constant ie22 UNITS | 0.0
19 | Constant ie31 UNITS | 0.0
20 | Constant ie33 UNITS | 0.0
21 | Constant if11 UNITS | 0.0
22 | Constant if22 UNITS | 0.0
23 | Constant ig11 UNITS | 0.0
24 | Constant ig22 UNITS | 0.0
25 | Constant l1 UNITS | 0.0
26 | Constant l2 UNITS | 0.0
27 | Constant l3 UNITS | 0.0
28 | Constant l4 UNITS | 0.0
29 | Constant mc UNITS | 0.0
30 | Constant md UNITS | 0.0
31 | Constant me UNITS | 0.0
32 | Constant mf UNITS | 0.0
33 | Constant mg UNITS | 0.0
34 | Constant rf UNITS | 0.0
35 | Constant rr UNITS | 0.0
36 |
37 | Initial Value q1 m | 0.0
38 | Initial Value q2 m | 0.0
39 | Initial Value q3 rad | 0.0
40 | Initial Value q4 rad | 0.0
41 | Initial Value q5 rad | 0.0
42 | Initial Value q6 rad | 0.0
43 | Initial Value q7 rad | 0.0
44 | Initial Value q8 rad | 0.0
45 | Initial Value q9 rad | 0.0
46 | Initial Value u4 rad/s | 0.0
47 | Initial Value u6 rad/s | 0.0
48 | Initial Value u7 rad/s | 0.0
49 | Initial Value u9 rad/s | 0.0
50 |
51 | Initial Time TINITIAL sec | 0.0
52 | Final Time TFINAL sec | 1.0
53 | Integration Step INTEGSTP sec | 0.1
54 | Print-Integer PRINTINT Positive Integer| 1
55 | Absolute Error ABSERR | 1.0E-08
56 | Relative Error RELERR | 1.0E-07
57 | ----------------------------------------------------------+------------------
58 |
59 | Note:
60 |
61 | INTEGSTP*PRINTINT is the time-interval for writing of output.
62 |
63 | ABSERR should be set equal to 10^(-8)*Xsmall, where Xsmall is the estimated
64 | smallest maximum absolute value of the variables being integrated.
65 |
66 | RELERR should be set equal to 10^(-d), where d is the desired number of
67 | significant digits of numerical integration results.
68 |
--------------------------------------------------------------------------------
/src/extensions/gyro/gyrobike.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file generates a non-linear simulation of the GyroBike at low speeds and
4 | # plots the results.
5 |
6 | import pickle
7 | from numpy import pi, sqrt
8 | import matplotlib.pyplot as plt
9 | import bicycleparameters as bp
10 | from dtk import bicycle
11 |
12 | try:
13 | # this file should be deleted if you want to force the simulation
14 | f = open('../../../data/extensions/gyrobikedata.p', 'r')
15 | except IOError:
16 | try:
17 | f = open('GyroBike.py', 'r')
18 | except IOError:
19 | from altk import alparse
20 | alparse.alparse('GyroBike', 'GyroBike', code='Python')
21 | else:
22 | f.close()
23 | del f
24 |
25 | from GyroBike import GyroBike
26 |
27 | # create the Whipple model (with my parameters)
28 | gyroNonLinear = GyroBike()
29 |
30 | # load the benchmark parameters
31 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
32 | gyro = bp.Bicycle('Gyro', pathToData, forceRawCalc=True)
33 | gyro.add_rider('Child', reCalc=True)
34 | benchmarkPar = bp.io.remove_uncertainties(gyro.parameters['Benchmark'])
35 | # convert to my parameter set
36 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
37 | moorePar['mg'] = benchmarkPar['mD']
38 | moorePar['ig11'] = benchmarkPar['IDxx']
39 | moorePar['ig22'] = benchmarkPar['IDyy']
40 | gyroNonLinear.set_parameters(moorePar)
41 |
42 | # set the initial conditions to match Meijaard2007
43 | speedNaught = 0.5
44 | rollRateNaught = 0.5
45 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
46 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
47 | wheelAngSpeed = -speedNaught / moorePar['rr']
48 | gyroNonLinear.initialConditions[gyroNonLinear.stateNames.index('q5')] = pitchAngle
49 | gyroNonLinear.initialConditions[gyroNonLinear.stateNames.index('u4')] = rollRateNaught
50 | gyroNonLinear.initialConditions[gyroNonLinear.stateNames.index('u6')] = wheelAngSpeed
51 | gyroNonLinear.initialConditions[gyroNonLinear.stateNames.index('u9')] = -5000. / 60. * 2 * pi
52 |
53 | # integration settings
54 | ts = 0.01 # step size
55 | tf = 5. # final time
56 | gyroNonLinear.intOpts['ts'] = ts
57 | gyroNonLinear.intOpts['tf'] = tf
58 |
59 | gyroNonLinear.simulate()
60 |
61 | # There is currenlty a bug in which the parameters do not get saved with
62 | # the gyroNonLinear object. Maybe they need to be copied or something...
63 | with open('../../../data/extensions/gyrobikedata.p', 'w') as f:
64 | pickle.dump(gyroNonLinear, f)
65 | else:
66 | with open('../../../data/extensions/gyrobikedata.p', 'r') as f:
67 | gyroNonLinear = pickle.load(f)
68 |
69 | # grab the interesting variables
70 | rollRate = gyroNonLinear.get_sim_output('u4')
71 | speed = -gyroNonLinear.get_sim_output('u6') * gyroNonLinear.parameters['rr']
72 | steerRate = gyroNonLinear.get_sim_output('u7')
73 | flywheelRate = gyroNonLinear.get_sim_output('u9') * -60. / 2. / pi
74 | time = gyroNonLinear.simResults['t']
75 |
76 | width = 5.0 # inches
77 | golden_ratio = (sqrt(5.0) - 1.0) / 2.0
78 | height = width * golden_ratio
79 | fig = plt.figure()
80 | fig.set_size_inches([width, height])
81 | params = {'backend': 'ps',
82 | 'axes.labelsize': 10,
83 | 'text.fontsize': 10,
84 | 'legend.fontsize': 8,
85 | 'xtick.labelsize': 8,
86 | 'ytick.labelsize': 8,
87 | 'text.usetex': True}
88 | plt.rcParams.update(params)
89 |
90 | fig.subplots_adjust(right=0.85, left=0.15, bottom=0.15)
91 |
92 | rateAxis = fig.add_subplot(2, 1, 1)
93 | speedAxis = fig.add_subplot(2, 1, 2)
94 | flywheelAxis = speedAxis.twinx()
95 |
96 | p1, = rateAxis.plot(time, rollRate, "k-",label="$u_4$")
97 | p2, = rateAxis.plot(time, steerRate, "k:", label="$u_7$")
98 | p3, = speedAxis.plot(time, speed, "k-", label="$v$")
99 | p4, = flywheelAxis.plot(time, flywheelRate, "k:", label="$-u_9$")
100 |
101 | rateAxis.set_ylabel('Angular Rate [rad/sec]')
102 | rateAxis.legend()
103 |
104 | speedAxis.set_ylabel('Speed [m/s]')
105 | speedAxis.set_xlabel('Time [sec]')
106 | flywheelAxis.set_ylabel('Angular Rate [rpm]')
107 | lines = [p3, p4]
108 | speedAxis.legend(lines, [l.get_label() for l in lines], loc=4)
109 |
110 | fig.savefig('../../../figures/extensions/gyro-nonlin-sim.png', dpi=300)
111 | fig.savefig('../../../figures/extensions/gyro-nonlin-sim.pdf')
112 |
--------------------------------------------------------------------------------
/src/extensions/gyro/gyrobike_linear.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import numpy as np
4 | import bicycleparameters as bp
5 | from dtk import bicycle
6 |
7 | try:
8 | f = open('GyroBike.py', 'r')
9 | except IOError:
10 | from altk import alparse
11 | alparse.alparse('GyroBike', 'GyroBike', code='Python')
12 | else:
13 | f.close()
14 | del f
15 |
16 | from GyroBike import LinearGyroBike
17 |
18 | # create the linear gyrobike model
19 | gyrobike = LinearGyroBike()
20 |
21 | # create the gyro bike
22 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
23 | gyro = bp.Bicycle('Gyro', pathToData, forceRawCalc=True)
24 |
25 | # set the model parameters
26 | benchmarkPar = bp.io.remove_uncertainties(gyro.parameters['Benchmark'])
27 | moorePar = bicycle.benchmark_to_moore(benchmarkPar)
28 | moorePar['mg'] = benchmarkPar['mD']
29 | moorePar['ig11'] = benchmarkPar['IDxx']
30 | moorePar['ig22'] = benchmarkPar['IDyy']
31 | gyrobike.set_parameters(moorePar)
32 |
33 | # set the default equilibrium point
34 | speedNaught = 0.5 # just over 1 mph, really slow walking
35 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'],
36 | moorePar['rr'], moorePar['d1'], moorePar['d2'], moorePar['d3'])
37 | wheelAngSpeed = -speedNaught / moorePar['rr']
38 | equilibrium = np.zeros(len(gyrobike.stateNames))
39 | equilibrium[gyrobike.stateNames.index('q5')] = pitchAngle
40 | equilibrium[gyrobike.stateNames.index('u6')] = wheelAngSpeed
41 |
42 | figDir = '../../../figures/extensions/'
43 |
44 | settings = {'num':100,
45 | 'axes':'parameter',
46 | 'pub':True,
47 | 'ylim':(-10, 10),
48 | 'width':5.0}
49 |
50 | # generate a root locus with respect to speed for a baseline
51 | gyrobike.linear(equilibrium)
52 | start = 0.
53 | stop = -10.0 / moorePar['rr']
54 | flywheelOff = gyrobike.plot_root_locus('u6', start, stop, factor=('$v$',
55 | -moorePar['rr']), units='m/s', **settings)
56 | flywheelOff.savefig(figDir + 'gyrobike-flywheel-off.png', dpi=300)
57 |
58 | # now generate a root locus with respect to flywheel speed
59 | gyrobike.linear(equilibrium)
60 | start = 0.
61 | stop = -10000. / 60. * 2 * np.pi
62 | flywheelOn = gyrobike.plot_root_locus('u9', start, stop, factor=('$-u_9$', -60. /
63 | 2 / np.pi), units='rpm', **settings)
64 | flywheelOn.savefig(figDir + 'gyrobike-vary-flywheel.png', dpi=300)
65 |
66 | # create the same graphs but with a rider
67 | # warning, as of now the arms are not properly set for this bicycle
68 | gyro.add_rider('Child')
69 | benchmarkPar = bp.io.remove_uncertainties(gyro.parameters['Benchmark'])
70 | moorePar = bicycle.benchmark_to_moore(benchmarkPar)
71 | moorePar['mg'] = benchmarkPar['mD']
72 | moorePar['ig11'] = benchmarkPar['IDxx']
73 | moorePar['ig22'] = benchmarkPar['IDyy']
74 | gyrobike.set_parameters(moorePar)
75 |
76 | # generate a root locus with respect to speed for a baseline
77 | gyrobike.linear(equilibrium)
78 | start = 0.
79 | stop = -10.0 / moorePar['rr']
80 | flywheelOffRider = gyrobike.plot_root_locus('u6', start, stop, factor=('$v$',
81 | -moorePar['rr']), units='m/s', **settings)
82 | flywheelOffRider.savefig(figDir + 'gyrobike-flywheel-off-rider.png', dpi=300)
83 |
84 | # now generate a root locus with respect to flywheel speed
85 | gyrobike.linear(equilibrium)
86 | start = 0.
87 | stop = -10000. / 60. * 2 * np.pi
88 | flywheelOnRider = gyrobike.plot_root_locus('u9', start, stop, factor=('$-u_9$', -60. /
89 | 2 / np.pi), units='rpm', **settings)
90 | flywheelOnRider.savefig(figDir + 'gyrobike-vary-flywheel-rider.png', dpi=300)
91 |
--------------------------------------------------------------------------------
/src/extensions/hips.py:
--------------------------------------------------------------------------------
1 | from numpy import load
2 | import matplotlib.pyplot as plt
3 |
4 | goldenRatio = (5**0.5 - 1.0) / 2.0
5 | fig_width = 4.0
6 | fig_height = fig_width * goldenRatio
7 | fig_size = [fig_width, fig_height]
8 | params = {'axes.labelsize': 8,
9 | 'axes.titlesize': 10,
10 | 'text.fontsize': 8,
11 | 'legend.fontsize': 8,
12 | 'xtick.labelsize': 8,
13 | 'ytick.labelsize': 8,
14 | 'text.usetex': True,
15 | 'figure.figsize': fig_size,
16 | }
17 | plt.rcParams.update(params)
18 |
19 | fileName = '/media/Data/Documents/School/TU Delft/MotionCapture/data/npy/hip/3104Hip.npy'
20 | HipVec = load(fileName)
21 |
22 | fig = plt.figure()
23 | ax = fig.add_subplot(1, 1, 1, aspect='equal')
24 | # right hip
25 | ax.plot(-HipVec[:, 0, 1], -HipVec[:, 0, 2], '.')
26 | # left hip
27 | ax.plot(-HipVec[:, 1, 1], -HipVec[:, 1, 2], '.')
28 | # plot butt
29 | ax.plot(-HipVec[:, 2, 1], -HipVec[:, 2, 2], '.')
30 | ax.set_ylim((0, .5))
31 | ax.set_ylabel('Distance [m]')
32 | ax.set_xlabel('Distance [m]')
33 | ax.legend(['Right Hip', 'Left Hip', 'Butt'])
34 | fig.subplots_adjust(bottom=0.15)
35 |
36 | fig.savefig('../../figures/extensions/hip-trace.png', dpi=300)
37 | fig.savefig('../../figures/extensions/hip-trace.pdf')
38 |
--------------------------------------------------------------------------------
/src/extensions/lateral/autolev_linear.py:
--------------------------------------------------------------------------------
1 | from altk import alutils
2 |
3 | matrices = ('A', 'B', 'C', 'D')
4 | states = ['q' + str(i + 1) for i in range(8)] + ['u4', 'u6', 'u7']
5 | inputs = ['Fcl', 'T4', 'T6', 'T7']
6 | outputs = ['q' + str(i + 1) for i in range(8)] + ['u' + str(i + 1) for i in range(8)]
7 |
8 | alutils.write_linearization(matrices, states, inputs, outputs,
9 | filename='lin.al')
10 |
--------------------------------------------------------------------------------
/src/extensions/lateral/lateral_force.m:
--------------------------------------------------------------------------------
1 | % This script generates both an impulse and frequency response plot for a
2 | % bicycle with respect to a lateral force applied to the bicycle frame.
3 |
4 | addpath('/media/Data/Documents/School/UC Davis/Bicycle Mechanics/HumanControl')
5 |
6 | pathToFile = ['/media/Data/Documents/School/UC Davis/Bicycle Mechanics/' ...
7 | 'HumanControl/parameters/RigidJasonPar.txt'];
8 |
9 | par = par_text_to_struct(pathToFile);
10 |
11 | % set the height of the force point such that 1 newton of lateral force
12 | % gives 1 n-m of torque about the wheel contact point line
13 | par.zcl = -1.0;
14 | %par.xcl = 0.0;
15 |
16 | speed = 7.0;
17 | [A, B, C, D] = whipple_pull_force_abcd(par, speed);
18 |
19 | bicycle = ss(A, B, C, D);
20 |
21 | [y, t] = impulse(bicycle);
22 |
23 | fig1 = figure();
24 | figWidth = 4.0;
25 | goldenRatio = (1 + sqrt(5)) / 2;
26 | figHeight = figWidth / goldenRatio;
27 | set(fig1, ...
28 | 'Color', [1, 1, 1], ...
29 | 'PaperOrientation', 'portrait', ...
30 | 'PaperUnits', 'inches', ...
31 | 'PaperPositionMode', 'manual', ...
32 | 'PaperPosition', [0, 0, figWidth, figHeight], ...
33 | 'PaperSize', [figWidth, figHeight])
34 | %'OuterPosition', [424, 305 - 50, 518, 465], ...
35 |
36 | lines = plot(t, y(:, 4, 1), 'b-', ...
37 | t, y(:, 7, 1), 'b--', ...
38 | t, y(:, 4, 3), 'r-', ...
39 | t, y(:, 7, 3), 'r--');
40 |
41 | set(lines, 'linewidth', 2.0)
42 | set(gca, 'TickDir', 'out', ...
43 | 'Box', 'off')
44 | title('Impulse Response')
45 | xlabel('Time [s]')
46 | ylabel('Angle [rad]')
47 | legend({'$q_4$ $(T_4)$', '$q_7$ $(T_4)$', '$q_4$ $(F_{cl})$', '$q_7$ $(F_{cl})$'}, 'interpreter', 'latex')
48 |
49 | print(fig1, '-dpng', '-r300', '../../../figures/extensions/lat-force-impulse.png')
50 | saveas(fig1, '../../../figures/extensions/lat-force-impulse.pdf')
51 |
52 | [num, den] = ss2tf(A, B, C, D, 1);
53 | rollFromTorque = tf(num(4, :), den);
54 | steerFromTorque = tf(num(7, :), den);
55 | [num, den] = ss2tf(A, B, C, D, 3);
56 | rollFromForce = tf(num(4, :), den);
57 | steerFromForce = tf(num(7, :), den);
58 |
59 | fig2 = figure();
60 | figWidth = 5.0;
61 | goldenRatio = (1 + sqrt(5)) / 2;
62 | figHeight = figWidth / goldenRatio;
63 | set(fig2, ...
64 | 'Color', [1, 1, 1], ...
65 | 'PaperOrientation', 'portrait', ...
66 | 'PaperUnits', 'inches', ...
67 | 'PaperPositionMode', 'manual', ...
68 | 'OuterPosition', [424, 305 - 50, 518, 465], ...
69 | 'PaperPosition', [0, 0, figWidth, figHeight], ...
70 | 'PaperSize', [figWidth, figHeight])
71 | hold all
72 | b = bodeplot(rollFromTorque, 'b-', ...
73 | steerFromTorque, 'b--', ...
74 | rollFromForce, 'r-', ...
75 | steerFromForce, 'r--', ...
76 | {1, 20});
77 | hold off
78 | bodeOpts = getoptions(b);
79 | bodeOpts.PhaseMatching = 'on';
80 | bodeOpts.PhaseMatchingFreq = 1;
81 | bodeOpts.PhaseMatchingValue = 0;
82 | bodeOpts.Title.String = '';
83 | setoptions(b,bodeOpts);
84 |
85 | plotAxes = findobj(fig2, 'type', 'axes');
86 |
87 | % there seems to be a bug such that the xlabel is too low, this is a hack to
88 | % get it to work
89 | raise = 0.05;
90 | set(plotAxes, 'XColor', 'k', 'YColor', 'k', 'Fontsize', 8)
91 | curPos1 = get(plotAxes(1), 'Position');
92 | curPos2 = get(plotAxes(2), 'Position');
93 | set(plotAxes(1), 'Position', curPos1 + [0, raise, 0, 0])
94 | set(plotAxes(2), 'Position', curPos2 + [0, raise, 0, 0])
95 | xLab = get(plotAxes(1), 'Xlabel');
96 | set(xLab, 'Units', 'normalized')
97 |
98 | legend({'$q_4$ $(T_4)$', '$q_7$ $(T_4)$', '$q_4$ $(F_{cl})$', '$q_7$ $(F_{cl})$'}, 'interpreter', 'latex')
99 | % find all the lines in the current figure
100 | lines = findobj(fig2, 'type', 'line');
101 | for i = 3:length(lines)
102 | set(lines, 'LineWidth', 2.0)
103 | end
104 |
105 | set(xLab, 'Position', get(xLab, 'Position') + [0, raise + 0.1, 0])
106 | print(fig2, '-dpng', '-r300', '../../../figures/extensions/lat-force-bode.png')
107 |
108 | % matlab sucks, the position is different in the pdf and the png and I can't
109 | % move it for the pdf in the correct position, the following two lines don't
110 | % seem to do anything
111 | xLab = get(plotAxes(1), 'Xlabel');
112 | set(xLab, 'Position', get(xLab, 'Position') + [0, -0.5, 0])
113 | saveas(fig2, '../../../figures/extensions/lat-force-bode.pdf')
114 |
--------------------------------------------------------------------------------
/src/extensions/lean/RiderLeanDynamics.in:
--------------------------------------------------------------------------------
1 | File: RiderLeanDynamics.in
2 | -----------------------------------------------------------------------------
3 | Note: Use spaces not tabs while editing this file.
4 | ----------------------------------------------------------+------------------
5 | Description Quantity Units | Value
6 | ----------------------------------------------------------|------------------
7 | Constant c9 UNITS | 0.0
8 | Constant d1 UNITS | 0.0
9 | Constant d2 UNITS | 0.0
10 | Constant d3 UNITS | 0.0
11 | Constant d4 UNITS | 0.0
12 | Constant g UNITS | 0.0
13 | Constant ic11 UNITS | 0.0
14 | Constant ic22 UNITS | 0.0
15 | Constant ic31 UNITS | 0.0
16 | Constant ic33 UNITS | 0.0
17 | Constant id11 UNITS | 0.0
18 | Constant id22 UNITS | 0.0
19 | Constant ie11 UNITS | 0.0
20 | Constant ie22 UNITS | 0.0
21 | Constant ie31 UNITS | 0.0
22 | Constant ie33 UNITS | 0.0
23 | Constant if11 UNITS | 0.0
24 | Constant if22 UNITS | 0.0
25 | Constant ig11 UNITS | 0.0
26 | Constant ig22 UNITS | 0.0
27 | Constant ig31 UNITS | 0.0
28 | Constant ig33 UNITS | 0.0
29 | Constant k9 UNITS | 0.0
30 | Constant l1 UNITS | 0.0
31 | Constant l2 UNITS | 0.0
32 | Constant l3 UNITS | 0.0
33 | Constant l4 UNITS | 0.0
34 | Constant l5 UNITS | 0.0
35 | Constant l6 UNITS | 0.0
36 | Constant lam UNITS | 0.0
37 | Constant mc UNITS | 0.0
38 | Constant md UNITS | 0.0
39 | Constant me UNITS | 0.0
40 | Constant mf UNITS | 0.0
41 | Constant mg UNITS | 0.0
42 | Constant rf UNITS | 0.0
43 | Constant rr UNITS | 0.0
44 |
45 | Initial Value q1 m | 0.0
46 | Initial Value q2 m | 0.0
47 | Initial Value q3 rad | 0.0
48 | Initial Value q4 rad | 0.0
49 | Initial Value q5 rad | 0.0
50 | Initial Value q6 rad | 0.0
51 | Initial Value q7 rad | 0.0
52 | Initial Value q8 rad | 0.0
53 | Initial Value q9 rad | 0.0
54 | Initial Value u4 rad/s | 0.0
55 | Initial Value u6 rad/s | 0.0
56 | Initial Value u7 rad/s | 0.0
57 | Initial Value u9 rad/s | 0.0
58 |
59 | Initial Time TINITIAL sec | 0.0
60 | Final Time TFINAL sec | 1.0
61 | Integration Step INTEGSTP sec | 0.1
62 | Print-Integer PRINTINT Positive Integer| 1
63 | Absolute Error ABSERR | 1.0E-08
64 | Relative Error RELERR | 1.0E-07
65 | ----------------------------------------------------------+------------------
66 |
67 | Note:
68 |
69 | INTEGSTP*PRINTINT is the time-interval for writing of output.
70 |
71 | ABSERR should be set equal to 10^(-8)*Xsmall, where Xsmall is the estimated
72 | smallest maximum absolute value of the variables being integrated.
73 |
74 | RELERR should be set equal to 10^(-d), where d is the desired number of
75 | significant digits of numerical integration results.
76 |
--------------------------------------------------------------------------------
/src/extensions/lean/riderlean.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import numpy as np
4 | import bicycleparameters as bp
5 | from dtk import bicycle
6 |
7 | try:
8 | f = open('RiderLean.py', 'r')
9 | except IOError:
10 | from altk import alparse
11 | alparse.alparse('RiderLean', 'RiderLean', code='Python')
12 | else:
13 | f.close()
14 | del f
15 |
16 | from RiderLean import LinearRiderLean
17 |
18 | # create the linear bike model
19 | bike = LinearRiderLean()
20 |
21 | # load the benchmark parameters
22 | pathToData='/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data/'
23 | bikeWithLegs = bp.Bicycle('Browserlegs', pathToData)
24 | #bikeWithLegs = bp.Bicycle('Lukelegs', pathToData)
25 | benchmarkPar = bp.io.remove_uncertainties(bikeWithLegs.parameters['Benchmark'])
26 |
27 | # convert to my parameter set
28 | moorePar = bicycle.benchmark_to_moore(benchmarkPar, oldMassCenter=False)
29 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
30 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
31 |
32 | # from my inertia model
33 | moorePar['d4'] = -1.011421527346159 + moorePar['rr']
34 | moorePar['l5'] = 0.231223496809401
35 | moorePar['l6'] = -1.283035074598082 + moorePar['rr'] - moorePar['d4']
36 | moorePar['lam'] = pitchAngle
37 | moorePar['mg'] = 48.816
38 | moorePar['ig11'] = 2.038073562525466
39 | moorePar['ig22'] = 1.692108544034995
40 | moorePar['ig33'] = 1.100753962972129
41 | moorePar['ig31'] = 0.002113501449812
42 | moorePar['k9'] = 0.0 #128.0
43 | moorePar['c9'] = 0.0 #50.0
44 | # from luke's paper
45 | #moorePar['d4'] = -0.9 + moorePar['rr']
46 | #moorePar['l5'] = 0.27
47 | #moorePar['l6'] = -0.99 + moorePar['rr'] - moorePar['d4']
48 | #moorePar['lam'] = pitchAngle
49 | #moorePar['mg'] = 51.0
50 | #moorePar['ig11'] = 4.299
51 | #moorePar['ig22'] = 5.186
52 | #moorePar['ig33'] = 1.413
53 | #moorePar['ig31'] = 1.444
54 | #moorePar['k9'] = 0.0
55 | #moorePar['c9'] = 0.0
56 | bike.set_parameters(moorePar)
57 |
58 | # set the equilibrium point
59 | speedNaught = 5.0
60 | pitchAngle = bicycle.pitch_from_roll_and_steer(0., 0., moorePar['rf'], moorePar['rr'],
61 | moorePar['d1'], moorePar['d2'], moorePar['d3'])
62 | wheelAngSpeed = -speedNaught / moorePar['rr']
63 | equilibrium = np.zeros(len(bike.stateNames))
64 | equilibrium[bike.stateNames.index('q5')] = pitchAngle
65 | equilibrium[bike.stateNames.index('u6')] = wheelAngSpeed
66 | bike.linear(equilibrium)
67 |
68 | settings = {'num':100,
69 | 'axes':'parameter',
70 | 'pub':True,
71 | #'ylim':(-10, 10),
72 | 'width':5.0}
73 |
74 | # plot a root locus
75 | start = 0.
76 | stop = -10.0 / moorePar['rr']
77 | free = bike.plot_root_locus('u6', start, stop,
78 | factor=('$v$', -moorePar['rr']), units='m/s', **settings)
79 | free.savefig('../../../figures/extensions/rider-lean.png', dpi=300)
80 | free.savefig('../../../figures/extensions/rider-lean.pdf')
81 |
82 | # set the stiffness and damping
83 | moorePar['k9'] = 128.0
84 | moorePar['c9'] = 50.0
85 | bike.set_parameters(moorePar)
86 | bike.linear(equilibrium)
87 | # plot a root locus
88 | start = 0.
89 | stop = -10.0 / moorePar['rr']
90 | free = bike.plot_root_locus('u6', start, stop,
91 | factor=('$v$', -moorePar['rr']), units='m/s', **settings)
92 | free.savefig('../../../figures/extensions/rider-lean-damp-stiff.png', dpi=300)
93 | free.savefig('../../../figures/extensions/rider-lean-damp-stiff.pdf')
94 |
--------------------------------------------------------------------------------
/src/load_paths.py:
--------------------------------------------------------------------------------
1 | import os
2 | from ConfigParser import SafeConfigParser
3 | config = SafeConfigParser()
4 | config.read(os.path.join(os.path.dirname(__file__), 'defaults.cfg'))
5 |
6 | def read(var):
7 | return config.get('data', var)
8 |
--------------------------------------------------------------------------------
/src/misc/convert_images.py:
--------------------------------------------------------------------------------
1 | #!usr/bin/env python
2 |
3 | import os
4 |
5 | def convert_all(directory, inputExt, outputExt, dpi=300):
6 | allFiles = os.listdir(directory)
7 | inputFiles = [x for x in allFiles if x.endswith(inputExt)]
8 | outputFiles = [os.path.splitext(x)[0] + '.' +outputExt for x in inputFiles]
9 | for i, o in zip(inputFiles, outputFiles):
10 | convert_image(directory + i, directory + o, dpi=dpi)
11 |
12 | def convert_image(inputFileName, outputFileName, dpi=300):
13 |
14 | os.system('convert -density ' + str(dpi) + ' "' + inputFileName +
15 | '" -density ' + str(dpi) + ' "' + outputFileName + '"')
16 |
--------------------------------------------------------------------------------
/src/motioncapture/coordinates.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This script remakes the coordinate time history plots in the ISEA2010 paper
4 | # with proper sizing and such.
5 |
6 | import string as st
7 | import numpy as np
8 | import matplotlib.pyplot as plt
9 |
10 | run = '3017'
11 |
12 | stateDir = '/media/Data/Documents/School/TU Delft/MotionCapture/data/npy/states/'
13 |
14 | q = np.load(stateDir + run + 'q.npy')
15 |
16 | # name the states
17 | qName = ['1 distance to rear wheel contact',
18 | '2 distance to the rear wheel contact',
19 | 'yaw angle',
20 | 'roll angle',
21 | 'pitch angle',
22 | 'steer angle',
23 | '1 distance to front wheel contact',
24 | '2 distance to front wheel contact',
25 | 'crank rotation',
26 | 'right knee lateral distance',
27 | 'left knee lateral distance',
28 | 'butt lateral distance',
29 | 'lean angle',
30 | 'twist angle']
31 |
32 | tSteps = len(q[0])
33 | t = np.linspace(0, 59.99, tSteps)
34 |
35 | goldenRatio = (5**0.5 - 1.0) / 2.0
36 | fig_width = 3.0
37 | fig_height = fig_width * goldenRatio
38 | fig_size = [fig_width, fig_height]
39 | params = {'axes.labelsize': 8,
40 | 'text.fontsize': 8,
41 | 'legend.fontsize': 6,
42 | 'xtick.labelsize': 8,
43 | 'ytick.labelsize': 8,
44 | 'text.usetex': True,
45 | 'savefig.dpi': 200,
46 | 'figure.subplot.hspace': 0.2,
47 | 'figure.subplot.bottom': 0.2,
48 | 'figure.subplot.left': 0.2,
49 | 'figure.figsize': fig_size}
50 |
51 | plt.rcParams.update(params)
52 |
53 | # wheel contact locations
54 | plt.figure(0)
55 | speed = 2.7778 # meters/second
56 | distance = speed * t
57 | plt.plot(q[0] + distance, 100 * q[7], '-', color='black',
58 | label='Rear Wheel')
59 | plt.plot(q[6] + distance, 100 * q[1], '-', color='grey',
60 | label='Front Wheel')
61 | plt.ylabel('Distance [cm]')
62 | plt.xlabel('Distance [m]')
63 | plt.xlim((q[0][0], distance[-1] + q[0][-1]))
64 | plt.legend()
65 |
66 | plt.savefig('../../figures/motioncapture/' + run + 'wheel.pdf')
67 | plt.savefig('../../figures/motioncapture/' + run + 'wheel.png')
68 |
69 | # yaw, roll and steer angle
70 | plt.figure(1)
71 | plt.plot(t, np.rad2deg(q[2]), '-', label=st.capwords(qName[2]),
72 | color='black', linewidth=2)
73 | plt.plot(t, np.rad2deg(q[3]), '-', label=st.capwords(qName[3]),
74 | color='grey', linewidth=2)
75 | plt.plot(t, np.rad2deg(q[5]), ':', label=st.capwords(qName[5]),
76 | color='black', linewidth=1)
77 | plt.legend()
78 | plt.ylabel('Angle [deg]')
79 | plt.xlabel('Time [s]')
80 | plt.ylim((-4, 4))
81 | plt.xlim((0, 10))
82 | plt.savefig('../../figures/motioncapture/' + run + 'bAng.pdf')
83 | plt.savefig('../../figures/motioncapture/' + run + 'bAng.png')
84 |
85 | # knee and butt lateral distance
86 | plt.figure(2)
87 | plt.subplot(211)
88 | plt.plot(t, 100.0 * q[9], '-', label=st.capwords(qName[9]),
89 | color='black', linewidth=2)
90 | plt.plot(t, 100.0 * q[10], '-', label=st.capwords(qName[10]),
91 | color='grey', linewidth=2)
92 | plt.legend(loc=0)
93 | plt.ylabel('Distance [cm]')
94 | plt.xlim((0, 10))
95 | plt.gca().set_xticklabels('')
96 |
97 | plt.subplot(212)
98 | plt.plot(t, 100.0 * q[11], '-', label=st.capwords(qName[11]),
99 | color='black', linewidth=2)
100 | plt.legend(loc = 0)
101 | plt.ylabel('Distance [cm]')
102 | plt.xlabel('Time [s]')
103 | plt.xlim((0, 10))
104 | plt.savefig('../../figures/motioncapture/' + run + 'rLat.pdf')
105 | plt.savefig('../../figures/motioncapture/' + run + 'rLat.png')
106 |
107 | # lean and twist
108 | plt.figure(3)
109 | plt.plot(t, np.rad2deg(q[12]), '-',
110 | label=st.capwords(qName[12]), color='black', linewidth=2)
111 | plt.plot(t, np.rad2deg(q[13]), '-',
112 | label=st.capwords(qName[13]), color='grey', linewidth=2)
113 | plt.legend()
114 | plt.ylabel('Angle [deg]')
115 | plt.xlabel('Time [s]')
116 | plt.xlim((0, 10))
117 | plt.savefig('../../figures/motioncapture/' + run + 'rAng.pdf')
118 | plt.savefig('../../figures/motioncapture/' + run + 'rAng.png')
119 |
--------------------------------------------------------------------------------
/src/parameterstudy/bicycle_bode_compare.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import matplotlib.pyplot as plt
4 | import bicycleparameters as bp
5 |
6 | # compare the benchmark parameters to some of the bicycles I measured
7 | pathToData = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data'
8 |
9 | browser = bp.Bicycle('Browser', pathToData=pathToData, forceRawCalc=True)
10 | browser.add_rider('Jason')
11 |
12 | goldenRatio = (5**0.5 - 1.0) / 2.0
13 | fig_width = 5.0
14 | fig_height = fig_width * goldenRatio
15 | fig_size = [fig_width,fig_height]
16 | params = {'axes.labelsize': 10,
17 | 'text.fontsize': 10,
18 | 'legend.fontsize': 10,
19 | 'xtick.labelsize': 8,
20 | 'ytick.labelsize': 8,
21 | 'text.usetex': True,
22 | 'figure.figsize': fig_size}
23 | plt.rcParams.update(params)
24 |
25 | speedFig = plt.figure()
26 |
27 | browser.compare_bode_speeds([0.5, 2.0, 6.0, 10.0], 1, 0, fig=speedFig)
28 | #sixLine = speedFig.ax2.lines[2]
29 | #sixLine.set_ydata(sixLine.get_ydata() + 360.)
30 | speedFig.savefig('../../figures/parameterstudy/bode-speeds.pdf')
31 | speedFig.savefig('../../figures/parameterstudy/bode-speeds.png', dpi=200)
32 |
33 | # compare heavy bicycle to light bicycle
34 | rigid = bp.Bicycle('Rigid', pathToData=pathToData, forceRawCalc=True)
35 | rigid.add_rider('Jason', reCalc=True)
36 |
37 | # add the rider from the rigid configuration to the pista, this normalizes the
38 | # rider inertial influence between bicycles
39 | pista = bp.Bicycle('Pista', pathToData=pathToData, forceRawCalc=True)
40 | pista.parameters['Benchmark'] = bp.rider.combine_bike_rider(pista.parameters['Benchmark'],
41 | rigid.riderPar['Benchmark'])
42 |
43 | weightFig = plt.figure()
44 | bp.compare_bode_bicycles([rigid, pista], 5.0, 1, 0, fig=weightFig)
45 | weightFig.savefig('../../figures/parameterstudy/bode-weight.pdf')
46 | weightFig.savefig('../../figures/parameterstudy/bode-weight.png', dpi=200)
47 |
--------------------------------------------------------------------------------
/src/parameterstudy/bicycle_eig_compare.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import numpy as np
4 | import bicycleparameters as bp
5 | from dtk import bicycle
6 |
7 | pathToData = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data'
8 | speeds = np.linspace(0., 10., num=100)
9 |
10 | # compare a bike with rider and bike without rider
11 | stratos = bp.Bicycle('Stratos', pathToData=pathToData, forceRawCalc=True)
12 | stratosRider = bp.Bicycle('Stratos', pathToData=pathToData, forceRawCalc=True)
13 | stratosRider.add_rider('Jason')
14 | compareRider = bp.plot_eigenvalues([stratos, stratosRider], speeds)
15 | ax = compareRider.axes[0]
16 | ax.set_ylim(-10, 10)
17 | ax.legend_.get_texts()[1].set_text('Stratos + Jason')
18 | compareRider.savefig('../../figures/parameterstudy/compare-rider-eig.png',
19 | dpi=200)
20 |
21 | # compare the benchmark parameters to some of the bicycles I measured
22 |
23 | benchmark = bp.Bicycle('Benchmark', pathToData=pathToData)
24 |
25 | browser = bp.Bicycle('Browser', pathToData=pathToData, forceRawCalc=True)
26 | browser.add_rider('Jason')
27 |
28 | stratos = bp.Bicycle('Stratos', pathToData=pathToData, forceRawCalc=True)
29 | stratos.add_rider('Jason')
30 |
31 | benchmarkReal = bp.plot_eigenvalues([benchmark, browser, stratos], speeds)
32 | ax = benchmarkReal.axes[0]
33 | ax.set_ylim(-10, 10)
34 | benchmarkReal.savefig('../../figures/parameterstudy/benchmark-real.png',
35 | dpi=200)
36 |
37 | # compare the silver bicycle parameters to some of the bicycles I measured
38 | bicycleNames = ['Silver',
39 | 'Crescendo',
40 | 'Fisher',
41 | 'Pista',
42 | 'Yellow']
43 |
44 | bicycles = [bp.Bicycle(name, pathToData=pathToData) for name in bicycleNames]
45 |
46 | silverCompare = bp.plot_eigenvalues(bicycles, speeds)
47 | ax = silverCompare.axes[0]
48 | ax.set_ylim(-10, 10)
49 | silverCompare.savefig('../../figures/parameterstudy/silver-compare.png',
50 | dpi=200)
51 |
52 | # compare the yellow bicycle configurations
53 | silver = bicycles[0]
54 | yellow = bicycles[-1]
55 | yellowRev = bp.Bicycle('Yellowrev', pathToData=pathToData, forceRawCalc=True)
56 | yellowCompare = bp.plot_eigenvalues([yellow, yellowRev, silver], speeds)
57 | ax = yellowCompare.axes[0]
58 | ax.set_ylim(-10, 10)
59 | yellowCompare.savefig('../../figures/parameterstudy/yellow-compare.png',
60 | dpi=200)
61 |
62 | # compare the browser configurations
63 | browser = bp.Bicycle('Browser', pathToData=pathToData, forceRawCalc=True)
64 | browserIns = bp.Bicycle('Browserins', pathToData=pathToData, forceRawCalc=True)
65 | browserCompare = bp.plot_eigenvalues([browser, browserIns], speeds)
66 | ax = browserCompare.axes[0]
67 | ax.set_ylim(-10, 10)
68 | browserCompare.savefig('../../figures/parameterstudy/browser-compare.png',
69 | dpi=200)
70 | # now add a rider
71 | browser.add_rider('Jason')
72 | browserIns.add_rider('Jason')
73 | browserRiderCompare = bp.plot_eigenvalues([browser, browserIns], speeds)
74 | ax = browserRiderCompare.axes[0]
75 | ax.set_ylim(-10, 10)
76 | browserRiderCompare.savefig('../../figures/parameterstudy/browser-rider-compare.png',
77 | dpi=200)
78 |
--------------------------------------------------------------------------------
/src/physicalparameters/bicycle_collage.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | # This makes a simple collage of all of the bicycle images.
4 |
5 | import os
6 |
7 | figPath = '../../figures/physicalparameters'
8 |
9 | bicycles = ['browser_sub.jpg',
10 | 'browserIns_sub.jpg',
11 | 'crescendo_sub.jpg',
12 | 'stratos_sub.jpg',
13 | 'fisher_sub.jpg',
14 | 'pista_sub.jpg',
15 | 'yellow_sub.jpg',
16 | 'yellowRev_sub.jpg',
17 | 'davisBicycle_sub.jpg',
18 | 'gyroBicycle_sub.jpg']
19 |
20 | names = ["'(a)'",
21 | "'(b)'",
22 | "'(c)'",
23 | "'(d)'",
24 | "'(e)'",
25 | "'(f)'",
26 | "'(g)'",
27 | "'(h)'",
28 | "'(i)'",
29 | "'(j)'"]
30 |
31 | args = ''
32 | for bike, name in zip(bicycles, names):
33 | args += '-label ' + name + ' ' + os.path.join(figPath, bike) + ' '
34 |
35 | widthSpacing = 0.0625 * 525.0 / 1.75
36 |
37 | heightSpacing = 10
38 |
39 | os.system('montage -density 300x300 -tile 2x5 -geometry +{:1.0f}+{:1.0f}'.format(widthSpacing,
40 | heightSpacing) + ' '
41 | + args + os.path.join(figPath, 'bicycles.jpg'))
42 |
--------------------------------------------------------------------------------
/src/physicalparameters/par_tables.py:
--------------------------------------------------------------------------------
1 | import os
2 | import bicycleparameters as bp
3 | from bicycleparameters.tables import Table
4 |
5 | pathToData = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data'
6 | pathToTables = '../../tables/physicalparameters'
7 |
8 | def create_tables(bikeNames, groupName):
9 | bikes = []
10 | for bike in bikeNames:
11 | bikes.append(bp.Bicycle(bike, pathToData=pathToData,
12 | forceRawCalc=True))
13 |
14 | for typ in ['Measured', 'Benchmark']:
15 | tab = Table(typ, True, bikes)
16 | pathToTableFile = os.path.join(pathToTables, groupName + typ + '.rst')
17 | tab.create_rst_table(fileName=pathToTableFile)
18 | print('Tables created for', bikeNames)
19 |
20 | # make a tables for the batavus bikes
21 | batavusNames = ['Browser', 'Browserins', 'Stratos']
22 | delftNames = ['Crescendo', 'Fisher', 'Pista']
23 | yellowNames = ['Yellow', 'Yellowrev']
24 | davisNames = ['Rigid', 'Rigidcl', 'Gyro']
25 | groupNames = ['batavus', 'delft', 'yellow', 'davis']
26 |
27 | for pair in zip([batavusNames, delftNames, yellowNames, davisNames],
28 | groupNames):
29 | create_tables(pair[0], pair[1])
30 |
--------------------------------------------------------------------------------
/src/systemidentification/canonical_fit_quality.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('..')
3 | from load_paths import read
4 | import cPickle
5 | import numpy as np
6 | import matplotlib.pyplot as plt
7 | import bicycledataprocessor as bdp
8 | from canonicalbicycleid import canonical_bicycle_id as cbi
9 |
10 | with open(read('pathToGoodRuns')) as f:
11 | goodRuns = cPickle.load(f)
12 |
13 | with open(read('pathToIdMat')) as f:
14 | idMat = cPickle.load(f)
15 |
16 | dataset = bdp.DataSet()
17 |
18 | roll = {k : [] for k in idMat.keys() + ['Whipple', 'Arm']}
19 | steer = {k : [] for k in idMat.keys() + ['Whipple', 'Arm']}
20 |
21 | allH = cbi.lateral_force_contribution(['Charlie', 'Jason', 'Luke'])
22 |
23 | for runNum in goodRuns:
24 | trial = bdp.Run(runNum, dataset, filterFreq=15.)
25 | rider = trial.metadata['Rider']
26 | timeseries = cbi.benchmark_time_series(trial, subtractMean=True)
27 | for k, model in idMat.items():
28 | rollrsq, steerrsq, fig = cbi.input_prediction(timeseries, model)
29 | roll[k].append(rollrsq)
30 | steer[k].append(steerrsq)
31 | fig.savefig('plots/' + str(runNum) + '-' + k + '.png')
32 | plt.close(fig)
33 | del fig
34 |
35 | M, C1, K0, K2 = trial.bicycle.canonical(nominal=True)
36 | rollrsq, steerrsq, fig = cbi.input_prediction(timeseries, (M, C1, K0, K2,
37 | allH[rider]))
38 | roll['Whipple'].append(rollrsq)
39 | steer['Whipple'].append(steerrsq)
40 | fig.savefig('plots/' + str(runNum) + '-whipple.png')
41 | plt.close(fig)
42 | del fig
43 |
44 | v = timeseries['v'].mean()
45 | A, B, speeds = cbi.mean_arm([rider])
46 | M = np.linalg.inv(B[round(v * 10)][2:, [0, 1]])
47 | C = -np.dot(M, A[round(v * 10)][2:, [2, 3]])
48 | K = -np.dot(M, A[round(v * 10)][2:, [0, 1]])
49 | rollrsq, steerrsq, fig = cbi.input_prediction(timeseries, (M, C, K,
50 | allH[rider]))
51 | roll['Arm'].append(rollrsq)
52 | steer['Arm'].append(steerrsq)
53 | fig.savefig('plots/' + str(runNum) + '-arm.png')
54 | plt.close(fig)
55 | del fig
56 |
--------------------------------------------------------------------------------
/src/systemidentification/canonical_fit_quality_results.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('..')
3 | from load_paths import read
4 |
5 | import os
6 | import cPickle
7 | import numpy as np
8 | import pandas
9 | import bicycledataprocessor as bdp
10 |
11 | # load in the raw results
12 | with open(read('pathToGoodRuns')) as f:
13 | goodRuns = cPickle.load(f)
14 |
15 | with open('rollFitPercent.p') as f:
16 | rollFit = cPickle.load(f)
17 |
18 | with open('steerFitPercent.p') as f:
19 | steerFit = cPickle.load(f)
20 |
21 | # get the rider, environment, maneuver, duration so we can sort by them
22 | dataset = bdp.DataSet()
23 | dataset.open()
24 |
25 | runTable = dataset.database.root.runTable
26 | taskTable = dataset.database.root.taskTable
27 |
28 | runIds = [bdp.database.run_id_string(r) for r in goodRuns]
29 |
30 | rider = []
31 | environment = []
32 | maneuver = []
33 | duration = []
34 |
35 | for rid in runIds:
36 | rowNum = bdp.database.get_row_num(rid, runTable)
37 | row = runTable[rowNum]
38 | rider.append(row['Rider'])
39 | environment.append(row['Environment'])
40 | maneuver.append(row['Maneuver'])
41 |
42 | rowNum = bdp.database.get_row_num(rid, taskTable)
43 | row = taskTable[rowNum]
44 | duration.append(row['Duration'])
45 |
46 | dataset.close()
47 |
48 | # build up a dictionary and data frame with all of the results
49 | dataDict = {}
50 |
51 | for k, v in rollFit.items():
52 | dataDict['roll-' + k] = v
53 |
54 | for k, v in steerFit.items():
55 | dataDict['steer-' + k] = v
56 |
57 | dataDict['duration'] = duration
58 | dataDict['environment'] = environment
59 | dataDict['rider'] = rider
60 | dataDict['maneuver'] = maneuver
61 |
62 | df = pandas.DataFrame(dataDict, index=runIds)
63 |
64 | # Create an integer weighting from 0 to 100 based on the duration of the runs.
65 | # The percent variance for long runs should be weighted more than those of
66 | # short runs because it is harder to get a good fit percentage for longer
67 | # duration runs.
68 | df['weights'] = np.int32(100 * df['duration'] / df['duration'].max())
69 |
70 | def compare(data, riders, environments, weighted=False):
71 | # subset the data frame based on riders and environments
72 | df2 = data[data['rider'].isin(riders)]
73 | df3 = df2[df2['environment'].isin(environments)]
74 | weights = df3['weights']
75 |
76 | steercols = [x for x in df3.columns if x.startswith('steer')]
77 | rollcols = [x for x in df3.columns if x.startswith('roll')]
78 | # now remove all but the steer values
79 | steerNumerics = df3.reindex(columns=steercols)
80 | rollNumerics = df3.reindex(columns=rollcols)
81 |
82 | def median(data, weighted):
83 | ans = {}
84 | for k, v in data.iteritems():
85 | if weighted is True:
86 | tot = []
87 | for i, fit in enumerate(v):
88 | tot += weights[i] * [fit]
89 | else:
90 | tot = v
91 | ans[k] = np.median(tot)
92 | return ans
93 |
94 | steerMedian = median(steerNumerics, weighted)
95 | rollMedian = median(rollNumerics, weighted)
96 |
97 | #def print_col(ans):
98 | #ans.sort(key=lambda x: x[1], reverse=True)
99 | #for a in ans:
100 | #print('{}: {:.1%}'.format(a[0], a[1]))
101 |
102 | #print_col(steerMedian)
103 | #print_col(rollMedian)
104 |
105 | return steerMedian, rollMedian
106 |
107 | sets = ['-'.join(x.split('-')[1:]) for x in df.columns
108 | if x.startswith('steer')]
109 | sets.remove('Whipple')
110 | sets.remove('Arm')
111 |
112 | rMap = {'J': ['Jason'],
113 | 'C': ['Charlie'],
114 | 'L': ['Luke'],
115 | 'A': ['Charlie', 'Jason', 'Luke']}
116 | eMap = {'P': ['Pavillion Floor'],
117 | 'H': ['Horse Treadmill'],
118 | 'A': ['Pavillion Floor', 'Horse Treadmill']}
119 |
120 | steerdf = {}
121 | rolldf = {}
122 | for s in sets:
123 | r, e = s.split('-')
124 | sMed, rMed = compare(df, rMap[r], eMap[e])
125 | sInd = sorted(sMed.keys())
126 | sInd.remove('steer-Whipple')
127 | sInd.remove('steer-Arm')
128 | sInd.append('steer-Whipple')
129 | sInd.append('steer-Arm')
130 | steerdf[s] = []
131 | for model in sInd:
132 | steerdf[s].append(sMed[model])
133 | rInd = sorted(rMed.keys())
134 | rInd.remove('roll-Whipple')
135 | rInd.remove('roll-Arm')
136 | rInd.append('roll-Whipple')
137 | rInd.append('roll-Arm')
138 | rolldf[s] = []
139 | for model in rInd:
140 | rolldf[s].append(rMed[model])
141 |
142 | sdf = pandas.DataFrame(steerdf, index=[x[6:] for x in sInd])
143 | rdf = pandas.DataFrame(rolldf, index=[x[5:] for x in rInd])
144 |
145 | # Create an restructuredtext table for each of the input measures.
146 | per = lambda x: '{:.1%}'.format(x)
147 |
148 | shtml = sdf.to_html(float_format=per)
149 | with open('steer.html', 'w') as f:
150 | f.write(shtml)
151 | os.system('pandoc -f html -t rst -o median-steer.rst steer.html')
152 | os.system('mv median-steer.rst ../../tables/systemidentification/')
153 |
154 | rhtml = rdf.to_html(float_format=per)
155 | with open('roll.html', 'w') as f:
156 | f.write(rhtml)
157 | os.system('pandoc -f html -t rst -o median-roll.rst roll.html')
158 | os.system('mv median-roll.rst ../../tables/systemidentification/')
159 |
--------------------------------------------------------------------------------
/src/systemidentification/canonical_plots.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('..')
3 | from load_paths import read
4 | import cPickle
5 |
6 | import numpy as np
7 | import matplotlib.pyplot as plt
8 | from dtk import bicycle, control
9 | from canonicalbicycleid import canonical_bicycle_id as cbi
10 |
11 | goldenRatio = (1. + np.sqrt(5.)) / 2.
12 | params = {'axes.labelsize': 10,
13 | 'axes.grid': False,
14 | 'text.fontsize': 10,
15 | 'legend.fontsize': 8,
16 | 'xtick.labelsize': 8,
17 | 'ytick.labelsize': 8,
18 | 'text.usetex': True,
19 | }
20 | plt.rcParams.update(params)
21 |
22 | pathToIdMat = read('pathToIdMat')
23 |
24 | with open(pathToIdMat) as f:
25 | idMat = cPickle.load(f)
26 |
27 | # First create all of the plots for the model identified from all riders and
28 | # all environments.
29 | allRiders = ['Charlie', 'Jason', 'Luke']
30 | # load M, C1, K0, K2 for each rider
31 | canon = cbi.load_benchmark_canon(allRiders)
32 | # load the H lateral force vector for each rider
33 | H = cbi.lateral_force_contribution(allRiders)
34 |
35 | # Eigenvalues versus speed plot.
36 | v0 = 0.
37 | vf = 10.
38 | num = 100
39 |
40 | # identified for all rider and all environments
41 | iM, iC1, iK0, iK2, iH = idMat['A-A']
42 | speeds, iAs, iBs = bicycle.benchmark_state_space_vs_speed(iM, iC1, iK0, iK2,
43 | v0=v0, vf=vf, num=num)
44 | w, v = control.eig_of_series(iAs)
45 | iEigenvalues, iEigenvectors = control.sort_modes(w, v)
46 |
47 | # whipple model (mean)
48 | wM, wC1, wK0, wK2, wH = cbi.mean_canon(allRiders, canon, H)
49 | speeds, wAs, wBs = bicycle.benchmark_state_space_vs_speed(wM, wC1, wK0, wK2,
50 | v0=v0, vf=vf, num=num)
51 | w, v = control.eig_of_series(wAs)
52 | wEigenvalues, wEigenvectors = control.sort_modes(w, v)
53 |
54 | # arm model (mean)
55 | aAs, aBs, aSpeed = cbi.mean_arm(allRiders)
56 | indices = np.int32(np.round(speeds * 10))
57 | w, v = control.eig_of_series(aAs[indices])
58 | aEigenvalues, aEigenvectors = control.sort_modes(w, v)
59 |
60 | rlfig = cbi.plot_rlocus_parts(speeds, iEigenvalues, wEigenvalues,
61 | aEigenvalues)
62 | rlfig.set_size_inches(5., 5. / goldenRatio)
63 | rlfig.savefig('../../figures/systemidentification/A-A-eig.png')
64 | rlfig.savefig('../../figures/systemidentification/A-A-eig.pdf')
65 |
66 | # Root locus with respect to speed.
67 | v0 = 0.
68 | vf = 10.
69 | num = 20
70 | speeds, iAs, iBs = bicycle.benchmark_state_space_vs_speed(iM, iC1, iK0, iK2,
71 | v0=v0, vf=vf, num=num)
72 | iEig, null = control.eig_of_series(iAs)
73 |
74 | speeds, wAs, wBs = bicycle.benchmark_state_space_vs_speed(wM, wC1, wK0, wK2,
75 | v0=v0, vf=vf, num=num)
76 | wEig, null = control.eig_of_series(wAs)
77 |
78 | indices = np.int32(np.round(speeds * 10))
79 | aEig, null = control.eig_of_series(aAs[indices])
80 | rlcfig = cbi.plot_rlocus(speeds, iEig, wEig, aEig)
81 | rlcfig.set_size_inches(4., 4.)
82 | rlcfig.savefig('../../figures/systemidentification/A-A-rlocus.png')
83 | rlcfig.savefig('../../figures/systemidentification/A-A-rlocus.pdf')
84 |
85 | # bode plots
86 | speeds = np.array([2.0, 4.0, 6.0, 9.0])
87 | null, iAs, iBs = bicycle.benchmark_state_space_vs_speed(iM, iC1, iK0, iK2,
88 | speeds)
89 | null, wAs, wBs = bicycle.benchmark_state_space_vs_speed(wM, wC1, wK0, wK2,
90 | speeds)
91 | figs = cbi.plot_bode(speeds, iAs, iBs, wAs, wBs, aAs, aBs)
92 | for fig in figs:
93 | fig.set_size_inches(5., 5.)
94 | leg = fig.phaseAx.legend(loc=4)
95 | plt.setp(leg.get_texts(), fontsize='5.0') #'xx-small')
96 |
97 | figs[0].savefig('../../figures/systemidentification/A-A-Tphi-Phi.png')
98 | figs[0].savefig('../../figures/systemidentification/A-A-Tphi-Phi.pdf')
99 |
100 | figs[1].savefig('../../figures/systemidentification/A-A-Tphi-Del.png')
101 | figs[1].savefig('../../figures/systemidentification/A-A-Tphi-Del.pdf')
102 |
103 | figs[2].savefig('../../figures/systemidentification/A-A-Tdel-Phi.png')
104 | figs[2].savefig('../../figures/systemidentification/A-A-Tdel-Phi.pdf')
105 |
106 | figs[3].savefig('../../figures/systemidentification/A-A-Tdel-Del.png')
107 | figs[3].savefig('../../figures/systemidentification/A-A-Tdel-Del.pdf')
108 |
--------------------------------------------------------------------------------
/src/systemidentification/canonical_table.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import cPickle
4 | from canonicalbicycleid import canonical_bicycle_id as cbi
5 | import pandas
6 |
7 | # Specify the data files
8 | dataDir = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/CanonicalBicycleID/data'
9 | pathToIDMat = os.path.join(dataDir, 'idMatrices.p')
10 | pathToCovar = os.path.join(dataDir, 'covarMatrices.p')
11 |
12 | with open(pathToIDMat) as f:
13 | idMat = cPickle.load(f)
14 |
15 | with open(pathToCovar) as f:
16 | covMat = cPickle.load(f)
17 | fNames = ['one', 'two', 'three']
18 |
19 | roll = [['Mpd', 'C1pd', 'K0pd'], [], []]
20 | steer = [[], ['Mdd', 'C1dp', 'C1dd'], ['K0dd', 'K2dd', 'HdF']]
21 |
22 | for n, r, s in zip(fNames, roll, steer):
23 | tableData = cbi.table_data(r, s, idMat, covMat)
24 | df = pandas.DataFrame(tableData)
25 | del df[0], df[1]
26 | for col in df.columns:
27 | if '%' in df[col][0]:
28 | del df[col]
29 | df = df.astype(float)
30 | print r, s
31 | print(df.mean())
32 | print(df.std())
33 | print(df.std() / abs(df.mean()) * 100)
34 | cbi.create_rst_table(tableData, r, s,
35 | fileName='../../tables/systemidentification/canonical-id-table-' +
36 | n + '.rst')
37 |
38 | # this computes the trail given the identified C1dp parameter
39 | rigid = cbi.bp.Bicycle('Rigid', '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleParameters/data')
40 | p = cbi.bp.io.remove_uncertainties(rigid.parameters['Benchmark'])
41 | SF = p['IFyy'] / p['rF']
42 | SR = p['IRyy'] / p['rR']
43 | ST = SR + SF
44 | for k, v in idMat.items():
45 | C1dp = v[1][1, 0]
46 | c = -p['w'] * (C1dp + SF * cbi.np.cos(p['lam'])) / ST / cbi.np.cos(p['lam'])
47 | print k, c
48 |
--------------------------------------------------------------------------------
/src/systemidentification/coefficient_plot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import sys
4 | sys.path.append('..')
5 | from load_paths import read
6 |
7 | import os
8 | import cPickle
9 | from numpy import linspace, sqrt, zeros
10 | from scipy.io import loadmat
11 | import matplotlib.pyplot as plt
12 | from matplotlib import rcParams
13 | from bicycleid import data, plot, model
14 | import dtk.bicycle
15 |
16 | params = {'axes.labelsize': 8,
17 | 'axes.titlesize': 10,
18 | 'text.fontsize': 10,
19 | 'legend.fontsize': 10,
20 | 'xtick.labelsize': 8,
21 | 'ytick.labelsize': 8,
22 | 'text.usetex': True}
23 | rcParams.update(params)
24 |
25 | dat = data.ExperimentalData()
26 |
27 | coefPlot = plot.CoefficientPlot()
28 |
29 | subDef = {}
30 | subDef['Rider'] = ['Charlie', 'Jason', 'Luke']
31 | subDef['Environment'] = ['Treadmill', 'Pavilion']
32 | subDef['Maneuver'] = ['Balance', 'Track Straight Line',
33 | 'Balance With Disturbance', 'Track Straight Line With Disturbance']
34 | subDef['Speed'] = ['1.4', '2.0', '3.0', '4.0', '4.92', '5.8', '7.0', '9.0']
35 | subDef['MeanFit'] = 0.0
36 | subDef['Duration'] = 0.0
37 |
38 | subDat = dat.subset(**subDef)
39 |
40 | speedRange = linspace(0.0, 10.0, num=50)
41 | models = {rider: model.Whipple(rider).matrices(speedRange) for rider in ['Charlie']}
42 |
43 | coefPlot.update_graph(subDat, models)
44 |
45 | # now add the arm model
46 | m = loadmat('../../data/extensions/armsAB-Charlie.mat', squeeze_me=True) # this is charlie at 101 speeds
47 |
48 | inputMats = zeros((101, 4, 1))
49 | for i, B in enumerate(m['inputMatrices']):
50 | inputMats[i] = B[:, 1].reshape(4, 1)
51 |
52 | for lab, ax in coefPlot.axes.items():
53 | row, col = int(lab[-2]), int(lab[-1])
54 | if lab[0] == 'a':
55 | ax.plot(m['speed'], m['stateMatrices'][:, row - 1, col - 1], 'r')
56 | elif lab[0] == 'b':
57 | ax.plot(m['speed'], inputMats[:, row - 1, col - 1], 'r')
58 |
59 | # now add the model identified from the runs with Luke on the Pavilion floor
60 | # with the canonical realization
61 | with open(read('pathToIdMat')) as f:
62 | idMat = cPickle.load(f)
63 |
64 | M, C1, K0, K2, H = idMat['L-P']
65 | speeds = linspace(0, 10, num=50)
66 | As = zeros((len(speeds), 4, 4))
67 | Bs = zeros((len(speeds), 4, 1))
68 |
69 | for i, v in enumerate(speeds):
70 | A, B = dtk.bicycle.benchmark_state_space(M, C1, K0, K2, v, 9.81)
71 | As[i] = A
72 | Bs[i] = B[:, 1].reshape(4, 1)
73 |
74 | for lab, ax in coefPlot.axes.items():
75 | row, col = int(lab[-2]), int(lab[-1])
76 | if lab[0] == 'a':
77 | ax.plot(speeds, As[:, row - 1, col - 1], 'orange')
78 | elif lab[0] == 'b':
79 | ax.plot(speeds, Bs[:, row - 1, col - 1], 'orange')
80 |
81 | width = 6.0
82 | coefPlot.title.set_fontsize(10.0)
83 | coefPlot.figure.set_figwidth(width)
84 | goldenRatio = (sqrt(5) - 1.0) / 2.0
85 | coefPlot.figure.set_figheight(6.0 * goldenRatio)
86 | coefPlot.figure.savefig('../../figures/systemidentification/coefficients.pdf')
87 | # this gtk backend failed when I tried to savefig a png, so I do this
88 | os.system('convert -density 200x200 ../../figures/systemidentification/coefficients.pdf ../../figures/systemidentification/coefficients.png')
89 |
--------------------------------------------------------------------------------
/src/systemidentification/compare_id_freq.py:
--------------------------------------------------------------------------------
1 | #!/usr/env python
2 |
3 | # Compare the frequency response of the identified models
4 |
5 | import sys
6 | sys.path.append('..')
7 | from load_paths import read
8 | import cPickle
9 |
10 | import numpy as np
11 | import matplotlib.pyplot as plt
12 | from dtk import bicycle, control
13 |
14 | pathToIdMat = read('pathToIdMat')
15 |
16 | with open(pathToIdMat) as f:
17 | idMat = cPickle.load(f)
18 |
19 | inputNames = ['$T_\phi$', '$T_\delta$']
20 | stateNames = ['$\phi$', '$\delta$', '$\dot{\phi}$', '$\dot{\delta}$']
21 | outputNames = ['$\phi$', '$\delta$']
22 |
23 | C = np.array([[1.0, 0.0, 0.0, 0.0],
24 | [0.0, 1.0, 0.0, 0.0]])
25 | D = np.array([[0.0, 0.0],
26 | [0.0, 0.0]])
27 |
28 |
29 | speeds = [2.0, 5.5, 9.0]
30 |
31 | linestyles = ['-'] * 3 + ['--'] * 3 + ['-.'] * 3 + [':'] * 3
32 |
33 | sysNames = sorted(idMat.keys())
34 |
35 | for speed in speeds:
36 | systems = []
37 | for k in sysNames:
38 |
39 | M, C1, K0, K2, H = idMat[k]
40 | A, B = bicycle.benchmark_state_space(M, C1, K0, K2, speed, 9.81)
41 |
42 | systems.append(control.StateSpace(A, B, C, D, name=k,
43 | stateNames=stateNames, inputNames=inputNames, outputNames=outputNames))
44 |
45 | w = np.logspace(-1, 2)
46 |
47 | bode = control.Bode(w, *systems, linestyles=linestyles)
48 | bode.plot()
49 |
50 | for f in bode.figs:
51 | leg = f.phaseAx.legend(loc=4)
52 | plt.setp(leg.get_texts(), fontsize='6.0') #'xx-small')
53 | bode.figs[2].savefig('../../figures/systemidentification/compare-id-bode-{}.png'.format(speed))
54 |
--------------------------------------------------------------------------------
/src/systemidentification/control_parameters_vs_speed_plots.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from scipy.optimize import curve_fit
3 | import matplotlib.pyplot as plt
4 |
5 | from load_rider_id_results import df
6 |
7 | # Plot parameters for Latex output
8 | params = {'backend': 'ps',
9 | 'axes.labelsize': 10,
10 | 'text.fontsize': 10,
11 | 'legend.fontsize': 8,
12 | 'xtick.labelsize': 8,
13 | 'ytick.labelsize': 8,
14 | 'text.usetex': True,
15 | 'figure.dpi': 200,
16 | }
17 | plt.rcParams.update(params)
18 |
19 | # Create a histogram of the speeds
20 | fig, ax = plt.subplots(1, 1, squeeze=True)
21 | fig.set_size_inches(5, 5)
22 | ax.hist(df['speed'], bins=40, range=(0, 10), align='mid')
23 | ax.set_xlabel('Speed m/s')
24 | ax.set_ylabel('Runs')
25 | ax.set_xticks(np.linspace(0, 10, 21))
26 | fig.savefig('../../figures/systemidentification/speed-hist-all.png')
27 | fig.savefig('../../figures/systemidentification/speed-hist-all.pdf')
28 |
29 | # plot the controller parameters versus speed
30 |
31 | fig, ax = plt.subplots(6, 1, sharex=True)
32 | fig.set_size_inches(6, 7.5)
33 |
34 | speedBins = np.linspace(0.0, 10.0, num=41)
35 | parameters = ['kDelta', 'kPhiDot', 'kPhi', 'kPsi', 'kYQ', 'wnm']
36 | plotData = {k : [] for k in parameters}
37 | nums = []
38 | meanFits = []
39 | for v in speedBins:
40 | sbin = df[(v - 0.25 < df['speed']) & (df['speed'] < v + 0.25)]
41 | nums.append(len(sbin))
42 | meanFits.append(sbin['fit'].mean())
43 | for par in parameters:
44 | plotData[par].append(sbin[par])
45 |
46 | widths = 0.25 * (np.sqrt(nums) / np.max(np.sqrt(nums)))
47 | line = lambda x, m, b: m * x + b
48 | constant = lambda x, c: c
49 | ylims = [(0, 100), (-8, 1), (1, 12), (-0.5, 3), (-0.5, 2), (0, 80)]
50 | for i, par in enumerate(parameters):
51 | # Fit a weight line through the medians.
52 | y = np.array([sbin.median() for sbin in plotData[par]])
53 | x = speedBins
54 | weight = np.array([sbin.std() for sbin in plotData[par]])
55 | if par == 'wnm':
56 | b, cov = curve_fit(constant, x[~np.isnan(y)], y[~np.isnan(y)],
57 | sigma=1. / widths[~np.isnan(y)])
58 | m = 0.0
59 | else:
60 | (m, b), cov = curve_fit(line, x[~np.isnan(y)], y[~np.isnan(y)],
61 | sigma=1. / widths[~np.isnan(y)])
62 | ax[i].plot(np.linspace(0, 10), m * np.linspace(0, 10) + b, '-g')
63 | ax[i].boxplot(plotData[par], positions=speedBins, widths=widths)
64 | ax[i].set_ylim(ylims[i])
65 | ax[i].set_ylabel(par)
66 | #xts = ax[i].get_xticks()
67 | xticks = np.linspace(0.0, 10.0, num=21)
68 | ax[i].set_xticks(xticks)
69 | #ax[i].set_xticks(xts - 0.25)
70 | #ax[i].set_xticklabels(speedBins)
71 | ax[-1].set_xlabel('Speed m/s')
72 |
73 | fig.savefig('../../figures/systemidentification/par-vs-speed-box-all.png')
74 | fig.savefig('../../figures/systemidentification/par-vs-speed-box-all.pdf')
75 | #top = ax[0].twiny()
76 | #top.set_xticks(ax[0].get_xticks() - 1.5)
77 | #top.set_xticklabels([''] + ['{}'.format(n) for n in nums])
78 |
--------------------------------------------------------------------------------
/src/systemidentification/crossover_plots.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 |
3 | from load_rider_id_results import df
4 |
5 | # Plot parameters for Latex output
6 | params = {'backend': 'ps',
7 | 'axes.labelsize': 10,
8 | 'text.fontsize': 10,
9 | 'legend.fontsize': 8,
10 | 'xtick.labelsize': 8,
11 | 'ytick.labelsize': 8,
12 | 'text.usetex': True,
13 | 'figure.dpi': 200,
14 | }
15 | plt.rcParams.update(params)
16 |
17 | fig = plt.figure()
18 | fig.set_size_inches(3, 3)
19 | df[['wcPhi', 'wcPsi', 'wcYQ']].boxplot()
20 | fig.savefig('../../figures/systemidentification/crossover-all.png')
21 | fig.savefig('../../figures/systemidentification/crossover-all.pdf')
22 |
--------------------------------------------------------------------------------
/src/systemidentification/data_histograms.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import bicycledataprocessor as bdp
4 |
5 | dataset = bdp.DataSet()
6 | dataset.open()
7 |
8 | rawMeta = dataset.database.root.runTable
9 |
10 | dates = []
11 |
12 | for d in rawMeta.cols.DateTime[:]:
13 | try:
14 | dates.append(bdp.main.matlab_date_to_object(d))
15 | except ValueError:
16 | pass
17 |
18 | runs = []
19 |
20 | sets = {}
21 | sets['Rider'] = set(rawMeta.cols.Rider)
22 | sets['Maneuver'] = set(rawMeta.cols.Maneuver)
23 | sets['Environment'] = set(rawMeta.cols.Environment)
24 | allSpeeds = set(rawMeta.cols.Speed)
25 | sets['Speed'] = []
26 | for speed in allSpeeds:
27 | if not np.isnan(speed):
28 | sets['Speed'].append(speed)
29 |
30 | num = {}
31 | for k, v in sets.items():
32 | num[k] = {}
33 | for unique in v:
34 | num[k][unique] = 0
35 |
36 | for row in rawMeta.iterrows():
37 | # this limits it to all the runs that are potentially valid for analyses
38 | con = []
39 | con.append(row['Rider'] in ['Jason', 'Charlie', 'Luke'])
40 | con.append(row['Maneuver'] == 'Balance' or
41 | row['Maneuver'] == 'Track Straight Line' or
42 | row['Maneuver'] == 'Balance With Disturbance' or
43 | row['Maneuver'] == 'Track Straight Line With Disturbance')
44 | con.append(row['corrupt'] is not True)
45 | con.append(int(row['RunID']) > 100)
46 | if False not in con:
47 | runs.append(row['RunID'])
48 | num['Rider'][row['Rider']] += 1
49 | num['Maneuver'][row['Maneuver']] += 1
50 | num['Environment'][row['Environment']] += 1
51 | num['Speed'][row['Speed']] += 1
52 |
53 | dataset.close()
54 |
55 | speeds = num['Speed']
56 | num['Speed'] = {}
57 | for k, v in speeds.items():
58 | num['Speed']['%1.2f' % k] = v
59 |
60 | fig_width = 4.0
61 | goldenRatio = (5**0.5 - 1.0) / 2.0
62 | fig_height = fig_width * goldenRatio
63 | fig_height = fig_width
64 | fig_size = [fig_width, fig_height]
65 | params = {'axes.labelsize': 8,
66 | 'axes.titlesize': 10,
67 | 'text.fontsize': 8,
68 | 'legend.fontsize': 8,
69 | 'xtick.labelsize': 6,
70 | 'ytick.labelsize': 6,
71 | 'text.usetex': True,
72 | 'figure.figsize': fig_size,
73 | 'figure.subplot.hspace': 0.5,
74 | 'figure.subplot.wspace': 0.2,
75 | 'figure.subplot.bottom': 0.1,
76 | 'figure.subplot.left': 0.1,
77 | 'figure.dpi': 200}
78 |
79 | plt.rcParams.update(params)
80 |
81 | fig = plt.figure()
82 |
83 | ax = fig.add_subplot(2, 2, 1)
84 | riders = ['Charlie', 'Jason', 'Luke']
85 | x = np.arange(len(riders))
86 | ax.bar(x, [num['Rider'][r] for r in riders], width=0.5)
87 | ax.set_xlim((x[0] - 0.5, x[-1] + 1.0))
88 | ax.set_ylabel(r'\# Runs')
89 | ax.set_title('Riders')
90 | plt.xticks(x + 0.25, riders)
91 |
92 | ax = fig.add_subplot(2, 2, 2)
93 | maneuvers = ['Balance', 'Balance With Disturbance',
94 | 'Track Straight Line', 'Track Straight Line With Disturbance']
95 | x = np.arange(len(maneuvers))
96 | ax.bar(x,[num['Maneuver'][m] for m in maneuvers], width=0.5)
97 | maneuvers = ['Balance', 'Balance\nWith Disturbance',
98 | 'Track\nStraight Line', 'Track Straight\nLine With Disturbance']
99 | ax.set_xlim((x[0] - 0.5, x[-1] + 1.0))
100 | ax.set_title('Maneuvers')
101 | plt.xticks(x + 0.25, maneuvers, fontsize=4, rotation=20.)
102 |
103 | ax = fig.add_subplot(2, 2, 3)
104 | environments = ['Horse Treadmill', 'Pavillion Floor']
105 | x = np.arange(len(environments))
106 | ax.bar(x, [num['Environment'][e] for e in environments], width=0.5)
107 | ax.set_xlim((x[0] - 0.5, x[-1] + 1.0))
108 | ax.set_ylabel(r'\# Runs')
109 | ax.set_title('Environments')
110 | plt.xticks(x + 0.25, environments)
111 |
112 | ax = fig.add_subplot(2, 2, 4)
113 | speeds = ['1.40', '2.00', '3.00', '4.00', '4.92', '5.80', '7.00', '9.00']
114 | x = np.arange(len(speeds))
115 | ax.bar(x, [num['Speed'][s] for s in speeds], width=0.5)
116 | ax.set_xlim((x[0] - 0.5, x[-1] + 1.0))
117 | ax.set_title('Speeds')
118 | plt.xticks(x + 0.25, speeds, rotation=-20)
119 |
120 | fig.savefig('../../figures/systemidentification/raw-data-bar-plot.png')
121 | fig.savefig('../../figures/systemidentification/raw-data-bar-plot.pdf')
122 |
--------------------------------------------------------------------------------
/src/systemidentification/fit_plots.m:
--------------------------------------------------------------------------------
1 | PATH_TO_BICYCLE_SYSTEM_ID = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleSystemID';
2 | addpath([PATH_TO_BICYCLE_SYSTEM_ID '/src/matlab'])
3 | dataDir = [PATH_TO_BICYCLE_SYSTEM_ID filesep 'scripts' filesep 'statespaceid' filesep 'exports'];
4 |
5 | inputs = {'tDelta', 'fB'};
6 | states = {'phi', 'delta', 'phiDot', 'deltaDot'};
7 | outputs = {'phi', 'delta', 'phiDot', 'deltaDot'};
8 |
9 | runID = '00596';
10 | [data, v, rider] = build_id_data([runID '.mat'], outputs, inputs, ...
11 | dataDir, true);
12 |
13 | whippleModel = bicycle_structured(['Rigid' rider], v, 'states', states, ...
14 | 'inputs', inputs, 'outputs', outputs);
15 |
16 | armModel = whippleModel;
17 | load(['../extensions/arms/armsAB-' rider '.mat'])
18 | armModel.A = squeeze(stateMatrices(round(v * 10) + 1, :, :));
19 | armModel.B = squeeze(inputMatrices(round(v * 10) + 1, :, [2, 3]));
20 |
21 | pemArgs = {'Maxiter', 100, ...
22 | 'SearchMethod', 'auto', ...
23 | 'Focus', 'Prediction', ...
24 | 'DisturbanceModel', 'none', ...
25 | 'InitialState', 'Estimate', ...
26 | 'Display', 'on', ...
27 | };
28 |
29 | identifiedModel = pem(data, whippleModel, pemArgs{:});
30 |
31 | [YH, FIT, X0] = compare(data, identifiedModel, whippleModel, armModel);
32 |
33 | time = data.SamplingInstants;
34 |
35 | fig = figure('Visible', 'off');
36 | goldenRatio = (1 + sqrt(5)) / 2;
37 | figWidth = 5;
38 | figHeight = figWidth * 1.25;
39 | set(gcf, ...
40 | 'Color', [1, 1, 1], ...
41 | 'PaperOrientation', 'portrait', ...
42 | 'PaperUnits', 'inches', ...
43 | 'PaperPositionMode', 'manual', ...
44 | 'OuterPosition', [424, 305 - 50, 518, 465], ...
45 | 'PaperPosition', [0, 0, figWidth, figHeight], ...
46 | 'PaperSize', [figWidth, figHeight])
47 |
48 | leftMargin = 0.16;
49 | % [gapHeight, gapWidth], [lowerMargin, upperMargin], [leftMargin, rigthMargin]
50 | axesHandles = tight_subplot(6, 1, [0.05, 0.0], [0.1, 0.05], [0.17, leftMargin]);
51 |
52 | % steer torque
53 | ax = axesHandles(1);
54 | % TODO: this title doesn't seem to want to show up
55 | title(ax, sprintf('Run # %s at %1.1f m/s', runID, v))
56 | lh = plot(ax, time, data.InputData(:, 1), 'k');
57 | ylabel(ax, '\(T_\delta\) [N-m]', 'Interpreter', 'Latex')
58 | xlim(ax, [0, 7])
59 | %set(ax, 'XTick', [])
60 |
61 | ax = axesHandles(2);
62 | lh = plot(ax, time, data.InputData(:, 2), 'k');
63 | ylabel(ax, '\(F_{c_l}\) [N]', 'Interpreter', 'Latex')
64 | ylim(ax, [-250, 250])
65 | xlim(ax, [0, 7])
66 | %set(ax, 'XTick', [])
67 |
68 | ylabels = {'\(\phi\) [rad]', '\(\delta\) [rad]',
69 | '$\dot{\phi}$ [rad/s]', '$\dot{\delta}$ [rad/s]'};
70 |
71 | f = squeeze(FIT);
72 | for i = 1:4
73 | ax = axesHandles(i + 2);
74 | lh = plot(ax, ...
75 | time, data.OutputData(:, i), 'k', ...
76 | time, YH{1}.OutputData(:, i), 'b', ...
77 | time, YH{2}.OutputData(:, i), 'g', ...
78 | time, YH{3}.OutputData(:, i), 'r');
79 | ylabel(ax, ylabels{i}, 'Interpreter', 'Latex')
80 | idLeg = sprintf('I (%1.0f%%)', f(1, i));
81 | whipLeg = sprintf('W (%1.0f%%)', f(2, i));
82 | armLeg = sprintf('A (%1.0f%%)', f(3, i));
83 | leg = legend(ax, 'M', idLeg, whipLeg, armLeg);
84 | % I can't figure out how to change the legend line length!! the
85 | % following doesn't seem to work
86 | legLines = findobj(leg, 'type', 'line');
87 | for i = 2:2:length(legLines)
88 | lim = get(legLines(i), 'XData');
89 | set(legLines(i), 'XData', [lim(1), lim(2) / 3])
90 | end
91 | set(leg, 'FontSize', 4)
92 | % TODO: I can't figure out how to tell the legend box to go exactly
93 | % where I want it to. It seems to be resized depending on what font size
94 | % is in the box and the space for the lines.
95 | axPos = get(ax, 'Position');
96 | pos = get(leg, 'Position');
97 | % left, bottom, width, height
98 | set(leg, 'OuterPosition',[0.86, axPos(2) + 0.05, 0.05, 0.01])
99 | %get(leg)
100 |
101 | xlim(ax, [0, 7])
102 | %if i ~= 4
103 | %set(ax, 'XTick', [])
104 | %end
105 | end
106 |
107 | % add the the time axis to the last bottom plot
108 | xlabel('Time [s]')
109 |
110 | saveas(fig, '../../figures/systemidentification/example-fit.png')
111 | saveas(fig, '../../figures/systemidentification/example-fit.pdf')
112 |
--------------------------------------------------------------------------------
/src/systemidentification/global_minima.m:
--------------------------------------------------------------------------------
1 | % This is an attempt to compare the identification as an output error model
2 | % and when estimating the Kalman gain matrix. It currently doesn't really
3 | % work well.
4 | PATH_TO_BICYCLE_SYSTEM_ID = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleSystemID';
5 | addpath([PATH_TO_BICYCLE_SYSTEM_ID '/src/matlab'])
6 | dataDir = [PATH_TO_BICYCLE_SYSTEM_ID filesep 'scripts' filesep 'statespaceid' filesep 'exports'];
7 |
8 | inputs = {'tDelta', 'fB'};
9 | states = {'phi', 'delta', 'phiDot', 'deltaDot'};
10 | outputs = {'phi', 'delta', 'phiDot', 'deltaDot'};
11 | outputs = {'delta', 'phiDot'};
12 |
13 | runID = '00700';
14 | [data, v, rider] = build_id_data([runID '.mat'], outputs, inputs, ...
15 | dataDir, true);
16 |
17 | whippleModel = bicycle_structured(['Rigid' rider], v, 'states', states, ...
18 | 'inputs', inputs, 'outputs', outputs);
19 |
20 | pemArgs = {'Maxiter', 100, ...
21 | 'SearchMethod', 'auto', ...
22 | 'Focus', 'Prediction', ...
23 | 'DisturbanceModel', 'none', ...
24 | 'InitialState', 'Estimate', ...
25 | 'Display', 'on', ...
26 | };
27 |
28 | identifiedModel = pem(data, whippleModel, pemArgs{:});
29 |
30 | pemArgs = {'Maxiter', 1000, ...
31 | 'SearchMethod', 'auto', ...
32 | 'Focus', 'Prediction', ...
33 | 'InitialState', 'Estimate', ...
34 | 'Display', 'on', ...
35 | };
36 |
37 | noiseStructured = whippleModel;
38 | noiseStructured.A = identifiedModel.A;
39 | noiseStructured.B = identifiedModel.B;
40 | % this doesn't seem to force the estimation of only the bottom two rows of
41 | % the K matrix, I'm not sure how to enforce it
42 | %noiseStructured.Ks = [0, 0, 0, 0;
43 | %0, 0, 0, 0;
44 | %nan, nan, nan, nan;
45 | %nan, nan, nan, nan];
46 | noiseStructured.Ks = [0, 0;
47 | 0, 0;
48 | nan, nan;
49 | nan, nan];
50 |
51 | % this currently runs to the 1000th iteration
52 | noiseModel = pem(data, noiseStructured, pemArgs{:});
53 |
54 | [YH, FIT, X0] = compare(data, identifiedModel, noiseModel);
55 |
56 | time = data.SamplingInstants;
57 |
58 | fig = figure('Visible', 'off');
59 | goldenRatio = (1 + sqrt(5)) / 2;
60 | figWidth = 5;
61 | figHeight = figWidth * 1.25;
62 | set(gcf, ...
63 | 'Color', [1, 1, 1], ...
64 | 'PaperOrientation', 'portrait', ...
65 | 'PaperUnits', 'inches', ...
66 | 'PaperPositionMode', 'manual', ...
67 | 'OuterPosition', [424, 305 - 50, 518, 465], ...
68 | 'PaperPosition', [0, 0, figWidth, figHeight], ...
69 | 'PaperSize', [figWidth, figHeight])
70 |
71 | leftMargin = 0.16;
72 | % [gapHeight, gapWidth], [lowerMargin, upperMargin], [leftMargin, rigthMargin]
73 | axesHandles = tight_subplot(6, 1, [0.05, 0.0], [0.1, 0.05], [0.17, leftMargin]);
74 |
75 | % steer torque
76 | ax = axesHandles(1);
77 | % TODO: this title doesn't seem to want to show up
78 | title(ax, sprintf('Run # %s at %1.1f m/s', runID, v))
79 | lh = plot(ax, time, data.InputData(:, 1), 'k');
80 | ylabel(ax, '\(T_\delta\) [N-m]', 'Interpreter', 'Latex')
81 | xlim(ax, [0, 7])
82 | %set(ax, 'XTick', [])
83 |
84 | ax = axesHandles(2);
85 | lh = plot(ax, time, data.InputData(:, 2), 'k');
86 | ylabel(ax, '\(F_{c_l}\) [N]', 'Interpreter', 'Latex')
87 | ylim(ax, [-250, 250])
88 | xlim(ax, [0, 7])
89 | %set(ax, 'XTick', [])
90 |
91 | ylabels = {'\(\phi\) [rad]', '\(\delta\) [rad]',
92 | '$\dot{\phi}$ [rad/s]', '$\dot{\delta}$ [rad/s]'};
93 |
94 | f = squeeze(FIT);
95 | for i = 1:length(outputs)
96 | ax = axesHandles(i + 2);
97 | lh = plot(ax, ...
98 | time, data.OutputData(:, i), 'k', ...
99 | time, YH{1}.OutputData(:, i), 'b', ...
100 | time, YH{2}.OutputData(:, i), 'r');
101 | ylabel(ax, ylabels{i}, 'Interpreter', 'Latex')
102 | idLeg = sprintf('I (%1.0f%%)', f(1, i));
103 | noiseLeg = sprintf('N (%1.0f%%)', f(2, i));
104 | leg = legend(ax, 'M', idLeg, noiseLeg);
105 | % I can't figure out how to change the legend line length!! the
106 | % following doesn't seem to work
107 | legLines = findobj(leg, 'type', 'line');
108 | for i = 2:2:length(legLines)
109 | lim = get(legLines(i), 'XData');
110 | set(legLines(i), 'XData', [lim(1), lim(2) / 3])
111 | end
112 | set(leg, 'FontSize', 4)
113 | % TODO: I can't figure out how to tell the legend box to go exactly
114 | % where I want it to. It seems to be resized depending on what font size
115 | % is in the box and the space for the lines.
116 | axPos = get(ax, 'Position');
117 | pos = get(leg, 'Position');
118 | % left, bottom, width, height
119 | set(leg, 'OuterPosition',[0.86, axPos(2) + 0.05, 0.05, 0.01])
120 | %get(leg)
121 |
122 | xlim(ax, [0, 7])
123 | %if i ~= 4
124 | %set(ax, 'XTick', [])
125 | %end
126 | end
127 |
128 | % add the the time axis to the last bottom plot
129 | xlabel('Time [s]')
130 |
131 | saveas(fig, '../../figures/systemidentification/global-minima.png')
132 | saveas(fig, '../../figures/systemidentification/global-minima.pdf')
133 |
--------------------------------------------------------------------------------
/src/systemidentification/load_rider_id_results.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | from scipy.io import loadmat
5 | import pandas
6 | import bicycledataprocessor as bdp
7 |
8 | # Load in the Matlab data
9 | pathToBicycleID = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleSystemID'
10 | results = loadmat(pathToBicycleID +
11 | '/data/riderid/bestControllerIdResults.mat', squeeze_me=True)
12 | crossover = loadmat(pathToBicycleID +
13 | '/data/riderid/crossoverFrequencies.mat', squeeze_me=True)
14 |
15 | # Build a dictionary of all the data
16 | dataDict = {}
17 | dataDict['speed'] = results['speeds']
18 | dataDict['fit'] = results['fits']
19 | dataDict['kDelta'] = results['parameters'][:, 0]
20 | dataDict['kPhiDot'] = results['parameters'][:, 1]
21 | dataDict['kPhi'] = results['parameters'][:, 2]
22 | dataDict['kPsi'] = results['parameters'][:, 3]
23 | dataDict['kYQ'] = results['parameters'][:, 4]
24 | dataDict['wnm'] = results['parameters'][:, 5]
25 | dataDict['zetanm'] = results['parameters'][:, 6]
26 | dataDict['runid'] = [os.path.splitext(str(x))[0] for x in results['matFiles']]
27 | dataDict['wcPhi'] = crossover['phi']
28 | dataDict['wcPsi'] = crossover['psi']
29 | dataDict['wcYQ'] = crossover['yQ']
30 |
31 | dataset = bdp.DataSet()
32 | dataset.open()
33 | runTable = dataset.database.root.runTable
34 | taskTable = dataset.database.root.taskTable
35 |
36 | dataDict['speedbin'] = []
37 | dataDict['maneuver'] = []
38 | dataDict['rider'] = []
39 | dataDict['duration'] = []
40 | dataDict['environment'] = []
41 |
42 | for runID in dataDict['runid']:
43 | rTabNum = bdp.database.get_row_num(runID, runTable)
44 | rRow = runTable[rTabNum]
45 | dataDict['maneuver'].append(rRow['Maneuver'])
46 | dataDict['rider'].append(rRow['Rider'])
47 | dataDict['environment'].append(rRow['Environment'])
48 | dataDict['speedbin'].append(rRow['Speed'])
49 | tTabNum = bdp.database.get_row_num(runID, taskTable)
50 | tRow = taskTable[tTabNum]
51 | dataDict['duration'].append(tRow['Duration'])
52 |
53 | dataset.close()
54 |
55 | # Create a pandas DataFrame
56 | df = pandas.DataFrame(dataDict)
57 | print(len(df))
58 | df = df.dropna(axis=0)
59 | print(len(df))
60 | df = df[(df['wnm'] < 100.0) & (df['fit'] > 10.0)]
61 | print(len(df))
62 |
--------------------------------------------------------------------------------
/src/systemidentification/output_fit_quality.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | # Creates restructured text tables with the results of the simulation output
4 | # percent variance for the set of runs.
5 |
6 | import os
7 | import numpy as np
8 | from scipy.io import loadmat
9 | import pandas
10 |
11 | directory = '/media/Data/Documents/School/UC Davis/Bicycle Mechanics/BicycleSystemID/scripts/canonicalid'
12 | fit = loadmat(os.path.join(directory, 'output-compare.mat'))
13 |
14 | # The simulations of the unstable system may blow up in the time span and the
15 | # resulting variance values are extremely small numbers. Here I replace those
16 | # numbers with NaN to ignore them in the following calculations.
17 | fit['fits'][fit['fits'] < -100.] = np.nan
18 |
19 | p = pandas.Panel(fit['fits'],
20 | items=[str(x[0]) for x in fit['goodRuns']],
21 | major_axis=[str(x[0][0]) for x in fit['models']],
22 | minor_axis=['phi', 'delta', 'phiDot', 'deltaDot'])
23 |
24 | # This gives the median value across runs for each model and output.
25 | outputMedian = p.median('items')
26 | # This gives the mean of the previous results across the outputs.
27 | totalMedian = p.median('items').mean(1)
28 |
29 | # Create an restructuredtext table for each of the input measures.
30 | per = lambda x: '{:.1%}'.format(x / 100.)
31 |
32 | html = outputMedian.to_html(float_format=per)
33 | with open('output-median.html', 'w') as f:
34 | f.write(html)
35 | os.system('pandoc -f html -t rst -o output-median.rst output-median.html')
36 | os.system('mv output-median.rst ../../tables/systemidentification/')
37 |
--------------------------------------------------------------------------------
/src/systemidentification/rider_id_model_quality_plot.py:
--------------------------------------------------------------------------------
1 | #/usr/bin/env python
2 |
3 | import numpy as np
4 | from scipy.io import loadmat
5 | import matplotlib.pyplot as plt
6 |
7 | # Plot parameters for Latex output
8 | params = {'backend': 'ps',
9 | 'axes.labelsize': 10,
10 | 'text.fontsize': 10,
11 | 'legend.fontsize': 8,
12 | 'xtick.labelsize': 8,
13 | 'ytick.labelsize': 8,
14 | 'text.usetex': True,
15 | 'figure.dpi': 200,
16 | }
17 | plt.rcParams.update(params)
18 |
19 | environments = ['treadmill', 'pavilion']
20 | files = ['riderIdTreadmill.mat', 'riderIdPavilion.mat']
21 |
22 | for filename, env in zip(files, environments):
23 | treadmillRun = loadmat('../../data/systemidentification/' +
24 | filename, squeeze_me=True)
25 |
26 | outputNames = [str(name) for name in treadmillRun['outputNames']]
27 | outputLabels = ['$\psi$ [rad]', '$\phi$ [rad]', '$\delta$ [rad]',
28 | '$\dot{\psi}$', '$\dot{\phi}$', '$\dot{\delta}$', '$T_\delta$']
29 | outputFit = treadmillRun['fit'][1]
30 |
31 | numSamp = treadmillRun['inputData'].shape[0]
32 | time = np.linspace(0, (numSamp - 1) / 200, num=numSamp)
33 |
34 | fig, axes = plt.subplots(8, 1, sharex=True)
35 | fig.set_size_inches(6, 8)
36 |
37 | for i, ax in enumerate(axes):
38 | box = ax.get_position()
39 | ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])
40 | if i == 0:
41 | ax.plot(time, treadmillRun['inputData'], 'k-', label='Measured')
42 | ax.set_ylabel('$F_B$')
43 | else:
44 | ax.plot(time, treadmillRun['outputData'][:, i - 1], 'k-', label='Measured')
45 | ax.plot(time, treadmillRun['simoY'][:, i - 1], 'b-', alpha=0.6,
46 | label='SIMO {:1.1f}\%'.format(outputFit[i - 1]))
47 | ax.set_ylabel(outputLabels[i - 1])
48 | ax.locator_params(axis='y', nbins=5)
49 | ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
50 | if env == 'treadmill':
51 | ax.set_xlim((53, 79))
52 |
53 | axes[3].plot(time, treadmillRun['sisoY'], 'g-', alpha=0.6,
54 | label='SISO {:1.1f}\%'.format(treadmillRun['fit'][1][4]))
55 | axes[3].legend(loc='center left', bbox_to_anchor=(1, 0.5))
56 |
57 | fig.savefig('../../figures/systemidentification/rider-id-' + env + '-run.png')
58 | fig.savefig('../../figures/systemidentification/rider-id-' + env + '-run.pdf')
59 |
--------------------------------------------------------------------------------
/src/systemidentification/run_time_history.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This creates two example time history plots, one for a treadmill run and one
4 | # for a pavilion run.
5 |
6 | import matplotlib.pyplot as plt
7 | import bicycledataprocessor as bdp
8 |
9 | dataset = bdp.DataSet()
10 |
11 | fig_width = 6.0
12 | goldenRatio = (5**0.5 - 1.0) / 2.0
13 | fig_height = fig_width * goldenRatio
14 | fig_size = [fig_width, fig_height]
15 | params = {'axes.labelsize': 8,
16 | 'axes.titlesize': 8,
17 | 'text.fontsize': 8,
18 | 'legend.fontsize': 6,
19 | 'xtick.labelsize': 6,
20 | 'ytick.labelsize': 6,
21 | 'text.usetex': True,
22 | 'figure.figsize': fig_size,
23 | #'figure.subplot.hspace': 0.3,
24 | 'figure.subplot.wspace': 0.25,
25 | #'figure.subplot.bottom': 0.1,
26 | #'figure.subplot.left': 0.1,
27 | 'figure.dpi': 300}
28 |
29 | plt.rcParams.update(params)
30 |
31 | for r, lims, tag in zip([283, 592], [(8., 24.), (0., 6.)], ['treadmill', 'pavilion']):
32 | trial = bdp.Run(r, dataset, filterFreq=15.)
33 |
34 | fig = plt.figure()
35 |
36 | sigs = trial.taskSignals
37 |
38 | plotSigs = [['YawRate', 'RollRate', 'PitchRate', 'SteerRate'],
39 | ['ForwardSpeed'],
40 | ['YawAngle', 'RollAngle', 'SteerAngle'],
41 | ['LateralRearContact', 'LateralFrontContact'],
42 | ['SteerTorque'],
43 | ['PullForce']]
44 |
45 | legends = [[r'$\dot{\psi}$', r'$\dot{\phi}$', r'$\dot{\theta}_B$', r'$\dot{\delta}$'],
46 | [r'$v$'],
47 | [r'$\psi$', r'$\phi$', r'$\delta$'],
48 | [r'$y_p$', r'$y_q$'],
49 | [r'$T_\delta$'],
50 | [r'$F_{c_l}$']]
51 |
52 | ylabels = ['Anglur Rate [rad/s]',
53 | 'Speed [m/s]',
54 | 'Angle [rad]',
55 | 'Distance [m]',
56 | 'Torque [N-m]',
57 | 'Force [n]']
58 |
59 | for i, group in enumerate(plotSigs):
60 | ax = fig.add_subplot(3, 2, i + 1)
61 | for s in group:
62 | time = sigs[s].time()
63 | ax.plot(time, sigs[s])
64 | ax.legend(legends[i])
65 | ax.set_xlim(lims)
66 | ax.set_ylabel(ylabels[i])
67 |
68 | fig.axes[-1].set_xlabel('Time [s]')
69 | fig.axes[-2].set_xlabel('Time [s]')
70 | l1 = r'Run \# {RunID}: {Rider} on the {Environment}'.format(**trial.metadata)
71 | l2 = r'performing {Maneuver} at speed {Speed} m/s'.format(**trial.metadata)
72 | fig.suptitle(l1 + '\n' + l2, fontsize=10)
73 |
74 | fig.savefig('../../figures/systemidentification/time-history-' + tag + '.png')
75 | fig.savefig('../../figures/systemidentification/time-history-' + tag +
76 | '.pdf')
77 | fig.savefig('../../figures/systemidentification/time-history-' + tag + '.png')
78 |
--------------------------------------------------------------------------------
/src/systemidentification/state_space_bode.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This compares the mean frequency response of all the state space identified
4 | # models with the Whipple and Arm models.
5 | import os
6 | from numpy import logspace, sqrt, array, zeros, log10, rad2deg
7 | from scipy.io import loadmat
8 | from matplotlib import rcParams
9 | from bicycleid import data, plot, model
10 | from dtk import control
11 |
12 | params = {'axes.labelsize': 8,
13 | 'text.fontsize': 8,
14 | 'legend.fontsize': 6,
15 | 'xtick.labelsize': 8,
16 | 'ytick.labelsize': 8,
17 | 'text.usetex': True}
18 | rcParams.update(params)
19 |
20 | subDef = {}
21 | subDef['Rider'] = ['Charlie', 'Jason', 'Luke']
22 | subDef['Environment'] = ['Treadmill', 'Pavilion']
23 | subDef['Maneuver'] = ['Balance', 'Track Straight Line',
24 | 'Balance With Disturbance', 'Track Straight Line With Disturbance']
25 | subDef['Speed'] = ['4.0']
26 | subDef['MeanFit'] = 0.0
27 | subDef['Duration'] = 0.0
28 |
29 | w = logspace(-1, 2, num=100)
30 | dat = data.ExperimentalData(w=w)
31 |
32 | models = {rider: model.Whipple(rider) for rider in ['Charlie']}
33 |
34 | bodePlot = plot.BodePlot(w)
35 | bodeData = dat.subset_bode(**subDef)
36 | bodePlot.update_graph(bodeData, models)
37 |
38 | # this is the Whipple model line
39 | bodePlot.bode.figs[0].axes[0].lines[3].set_color('g')
40 | bodePlot.bode.figs[0].axes[1].lines[3].set_color('g')
41 |
42 | # arm model at the mean speed for this run
43 | m = loadmat('../extensions/arms/armsAB-Charlie.mat', squeeze_me=True) # this is charlie at 101 speeds
44 |
45 | # @ 4.0 m/s
46 | A = m['stateMatrices'][40]
47 | B = m['inputMatrices'][40, :, 1].reshape(4, 1)
48 | C = array([[1.0, 0.0, 0.0, 0.0]])
49 | D = zeros((1, 1))
50 | sys = control.StateSpace(A, B, C, D)
51 | mag, phase = bodePlot.bode.mag_phase_system(sys)
52 |
53 | # arm model lines
54 | # mag
55 | bodePlot.bode.figs[0].axes[0].lines[4].set_ydata(20 * log10(mag[:, 0, 0]))
56 | bodePlot.bode.figs[0].axes[0].lines[4].set_color('r')
57 | # phase
58 | bodePlot.bode.figs[0].axes[1].lines[4].set_ydata(rad2deg(phase[:, 0, 0]))
59 | bodePlot.bode.figs[0].axes[1].lines[4].set_color('r')
60 |
61 | bodePlot.bode.figs[0].axes[0].set_ylim((-120, 0))
62 | bodePlot.bode.figs[0].axes[1].set_ylim((-250, 0))
63 |
64 | # move the y axis labels
65 | x, y = bodePlot.bode.figs[0].axes[0].yaxis.get_label().get_position()
66 | bodePlot.bode.figs[0].axes[0].yaxis.set_label_coords(x - 0.1, y)
67 |
68 | x, y = bodePlot.bode.figs[0].axes[1].yaxis.get_label().get_position()
69 | bodePlot.bode.figs[0].axes[1].yaxis.set_label_coords(x - 0.1, y)
70 |
71 | goldenRatio = (1 + sqrt(5.)) / 2.
72 | figWidth = 5.
73 | figHeight = figWidth / goldenRatio
74 |
75 | bodePlot.bode.figs[0].set_figwidth(figWidth)
76 | bodePlot.bode.figs[0].set_figheight(figHeight)
77 | bodePlot.bode.figs[0].savefig('../../figures/systemidentification/state-space-bode.pdf')
78 | # following line isn't working
79 | #bodePlot.bode.figs[0].savefig('../../figures/systemidentification/state-space-bode.png')
80 | os.system('convert ../../figures/systemidentification/state-space-bode.pdf ../../figures/systemidentification/state-space-bode.png')
81 |
--------------------------------------------------------------------------------
/src/systemidentification/tight_subplot.m:
--------------------------------------------------------------------------------
1 | function ha = tight_subplot(Nh, Nw, gap, marg_h, marg_w)
2 |
3 | % tight_subplot creates "subplot" axes with adjustable gaps and margins
4 | %
5 | % ha = tight_subplot(Nh, Nw, gap, marg_h, marg_w)
6 | %
7 | % in: Nh number of axes in hight (vertical direction)
8 | % Nw number of axes in width (horizontaldirection)
9 | % gap gaps between the axes in normalized units (0...1)
10 | % or [gap_h gap_w] for different gaps in height and width
11 | % marg_h margins in height in normalized units (0...1)
12 | % or [lower upper] for different lower and upper margins
13 | % marg_w margins in width in normalized units (0...1)
14 | % or [left right] for different left and right margins
15 | %
16 | % out: ha array of handles of the axes objects
17 | % starting from upper left corner, going row-wise as in
18 | % going row-wise as in
19 | %
20 | % Example: ha = tight_subplot(3,2,[.01 .03],[.1 .01],[.01 .01])
21 | % for ii = 1:6; axes(ha(ii)); plot(randn(10,ii)); end
22 | % set(ha(1:4),'XTickLabel',''); set(ha,'YTickLabel','')
23 | %
24 | % Pekka Kumpulainen 20.6.2010 @tut.fi
25 | % Tampere University of Technology / Automation Science and Engineering
26 |
27 | % Copyright (c) 2010, Pekka Kumpulainen
28 | % All rights reserved.
29 | %
30 | % Redistribution and use in source and binary forms, with or without
31 | % modification, are permitted provided that the following conditions are
32 | % met:
33 | %
34 | % * Redistributions of source code must retain the above copyright
35 | % notice, this list of conditions and the following disclaimer.
36 | % * Redistributions in binary form must reproduce the above copyright
37 | % notice, this list of conditions and the following disclaimer in
38 | % the documentation and/or other materials provided with the distribution
39 | %
40 | %THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
41 | %AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42 | %IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
43 | %ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
44 | %LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
45 | %CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
46 | %SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
47 | %INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
48 | %CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49 | %ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
50 | %POSSIBILITY OF SUCH DAMAGE.
51 |
52 |
53 | if nargin<3; gap = .02; end
54 | if nargin<4 || isempty(marg_h); marg_h = .05; end
55 | if nargin<5; marg_w = .05; end
56 |
57 | if numel(gap)==1;
58 | gap = [gap gap];
59 | end
60 | if numel(marg_w)==1;
61 | marg_w = [marg_w marg_w];
62 | end
63 | if numel(marg_h)==1;
64 | marg_h = [marg_h marg_h];
65 | end
66 |
67 | axh = (1-sum(marg_h)-(Nh-1)*gap(1))/Nh;
68 | axw = (1-sum(marg_w)-(Nw-1)*gap(2))/Nw;
69 |
70 | py = 1-marg_h(2)-axh;
71 |
72 | ha = zeros(Nh*Nw,1);
73 | ii = 0;
74 | for ih = 1:Nh
75 | px = marg_w(1);
76 |
77 | for ix = 1:Nw
78 | ii = ii+1;
79 | ha(ii) = axes('Units','normalized', ...
80 | 'Position',[px py axw axh], ...
81 | 'XTickLabel','', ...
82 | 'YTickLabel','');
83 | px = px+axw+gap(2);
84 | end
85 | py = py-axh-gap(1);
86 | end
87 |
88 |
--------------------------------------------------------------------------------
/todo.rst:
--------------------------------------------------------------------------------
1 | =========
2 | TODO List
3 | =========
4 |
5 | Major
6 | =====
7 |
8 | Minor
9 | =====
10 |
11 | - Add notation list for the system id chapter.
12 | - Add m/s to all of the km/h and mph numbers.
13 | - Write an overall conclusion.
14 | - Try to say more about tables 13.4 5 and 6
15 | - sphinxcontrib-bibtex is not resolving hyper links in the pdf in the same way
16 | the footnotes do. I think this only affects the citations, footnotes, and
17 | todo items in the auto-built area.
18 | - The footnotes in the latexpdf hyperlink to page i. These don't really need to
19 | be hyperlinks at all.
20 | - The latex build wants the target directive in images to point to something
21 | different than the html.
22 | - Center tables in html.
23 | - Citations for all software packages.
24 | - Add figures in the sys id rider section that compare the factors
25 | - Add an eigenvalue vs speed plot of the identified closed loop poles of the
26 | full rider/bicycle system.
27 | - src/extensions/arms/plot_eig.py seems to no longer exist anywhere...rewrite?
28 | - Formulas for the gains and a plot of eigenvalue of forward speed with respect
29 | to those formulas for the closed loop system. Make sure you no where the L-P
30 | model numbers are. Maybe make them explicit again in this section.
31 | - Plot of gains versus speed (theoretical) for the L-P model instead of the
32 | Whipple model.
33 |
34 | Collected
35 | =========
36 |
37 | .. todolist::
38 |
--------------------------------------------------------------------------------
/word_count.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """This script counts the number of words at each commit and plots a graph of
4 | word count versus time."""
5 |
6 | import os
7 |
8 | import pandas as pd
9 | import matplotlib.pyplot as plt
10 |
11 |
12 | sh_command = """\
13 | rm count.txt
14 | for commit in `git rev-list --all`; do
15 | git log -n 1 --pretty=%ad $commit >> count.txt;
16 | git archive $commit | tar -x -O --wildcards '*.rst' | wc -w >> count.txt;
17 | done\
18 | """
19 | os.system(sh_command)
20 |
21 | data = {'date': [], 'count': []}
22 |
23 | with open('count.txt', 'r') as f:
24 | for line in f:
25 | if '-' in line:
26 | data['date'].append(pd.to_datetime(line.strip().split(' -')[0]))
27 | else:
28 | data['count'].append(line.strip())
29 |
30 | df = pd.Series(data['count'], data['date'])
31 | df = df[df != '0']
32 | fig, ax = plt.subplots()
33 | df.astype(float).plot(ax=ax)
34 | ax.axvline(pd.to_datetime('August 22, 2012'), color='black')
35 | ax.set_ylabel('Word Count')
36 | ax.set_xlabel('Date')
37 | plt.tight_layout()
38 | plt.show()
39 |
--------------------------------------------------------------------------------
/zreferences.rst:
--------------------------------------------------------------------------------
1 | References
2 | ==========
3 |
4 | .. bibliography:: data/bicycle.bib
5 | :encoding: latex+utf8
6 | :style: alpha
7 |
--------------------------------------------------------------------------------