├── .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 Disqus 51 | 52 | {% endblock %} 53 | {% block footer %} 54 | {{ super() }} 55 | 63 | 64 |
65 | 66 | Creative Commons License 69 | 70 |
71 | This work is licensed under a Creative Commons 73 | Attribution 3.0 Unported License 74 |
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 | --------------------------------------------------------------------------------