System
- A Mechanical System¶
The System
object is the central component for modeling a
mechanical system, and usually the first trep
object you
create. It contains the entire definition of the system, including
all coordinate frames, configuration variables, constraints, etc.
System
is responsible for calculating continuous dynamics and
derivative, but it is also used for the underlying Lagrangian
calculations in any discrete dynamics calculations.
A System
has a single inertial coordinate frame, accessible
through the world_frame
attribute. Every other coordinate
frame added to the system will be descended from this coordinate
frame.
-
class
trep.
System
¶ Create a new empty mechanical system.
System Components¶
-
System.
world_frame
¶ The root spatial frame of the system. The world frame will always exist and cannot be changed other than adding child frames.
(read only)
-
System.
frames
¶ -
System.
configs
¶ -
System.
dyn_configs
¶ -
System.
kin_configs
¶ -
System.
potentials
¶ -
System.
forces
¶ -
System.
inputs
¶ -
System.
constraints
¶ Tuples of all the components in the system. The order of components in these tuples defines their order throughout
trep
. Any vector of configuration values will be the same order asSystem.configs
, any vector of constraint forces will be the same order asSystem.constraints
, etc.For example, any array of numbers representing a configuration will be ordered according to
System.configs
. An array of constraint forces will correspond to the order ofSystem.constraints
.configs
is guaranteed to be ordered as the concatenation ofdyn_configs
andkin_configs
:System.configs = System.dyn_configs + System.kin_configs
The tuples are read only, but the components in them may be modified.
-
System.
masses
¶ A tuple of all the
Frame
objects in the system with non-zero mass properties.(read only)
-
System.
nQ
¶ Number of configuration variables in the system. Equivalent to
len(system.configs)
.(read only)
-
System.
nQd
¶ Number of dynamic configuration variables in the system. Equivalent to
len(system.dyn_configs)
.(read only)
-
System.
nQk
¶ Number of kinematic configuration variables in the system. Equivalent to
len(system.kin_configs)
.(read only)
-
System.
nu
¶ Number of inputs in the system. Equivalent to
len(system.inputs)
.(read only)
-
System.
nc
¶ Number of constraints in the system. Equivalent to
len(system.constraints)
.(read only)
Finding Specific Components¶
-
System.
get_frame
(identifier)¶ -
System.
get_config
(identifier)¶ -
System.
get_potential
(identifier)¶ -
System.
get_constraint
(identifier)¶ -
System.
get_force
(identifier)¶ -
System.
get_input
(identifier)¶ Find a specific component in the system. The identifier can be:
Integer: Returns the component at that position in the corresponding tuple.
system.get_frame(i) = system.frames[i]
If the index is invalid, an
IndexError
exception is raised.String: Returns the component with the matching name:
system.get_config(‘theta’).name == ‘theta’
If no object has a matching name, a
KeyError
exception is raised.Object: Return the identifier unmodified:
system.get_force(force) == force
If the object is the incorrect type, a
TypeError
exception is raised:>>> config = system.configs[0] >>> system.get_frame(config) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python2.7/dist-packages/trep/system.py", line 106, in get_frame return self._get_object(identifier, Frame, self.frames) File "/usr/local/lib/python2.7/dist-packages/trep/system.py", line 509, in _get_object raise TypeError() TypeError
Importing and Exporting Frame Definitions¶
-
System.
import_frames
(children)¶ Adds children to this system’s world frame using a special frame definition. See
Frame.import_frames()
for details.
-
System.
export_frames
(system_name='system', frames_name='frames', tab_size=4)¶ Create python source code to define this system’s frames. The code is returned as a string.
System State¶
A System
is a stateful object that has a current time,
configuration, velocity, and input at all times. When working
directly with a System
, you are a responsible for setting the
state.
-
System.
t
¶ Current time of the system.
The other state information is actually spread out among each
component in the system. For example, each current configuration
value is stored in Config.q
. These can be modified directly
through each component, but the following attributes are usually more
convenient for reading and writing multiple values at once.
-
System.
q
¶ -
System.
dq
¶ -
System.
ddq
¶ The value, velocity, and acceleration of the complete configuration.
-
System.
qd
¶ -
System.
dqd
¶ -
System.
ddqd
¶ The value, velocity, and acceleration of the dynamic configuration.
-
System.
qk
¶ -
System.
dqk
¶ -
System.
ddqk
¶ The value, velocity, and acceleration of the kinematic configuration.
-
System.
u
¶ The current values of the force inputs.
Reading the each attribute will return a numpy array of the current values.
The values can be set with three different methods:
A dictionary that maps names to values:
>>> system.q = { 'x' : 1.0, 'theta' : 0.1}Any variables that are not named will be unchanged.
A array-like list of numbers:
>>> system.q = [1.0, 0.1, 0.0, 2.3] >>> system.q = np.array([1.0, 0.1, 0.0, 2.3])If the size of the array and the number of variables doesn’t match, the shorter of the two will be used:
>>> system.nQ 4 # This only sets the first 2 configuration variables! >>> system.q = [0.2, 5.0] # This ignores the last 2 values! >>> system.q = [0.5, 0, 1.1, 2.4, 9.0]A single number:
>>> system.q = 0This will set the entire configuration to the number.
-
System.
set_state
(q=None, dq=None, u=None, ddqk=None, t=None)¶ Set the current state of the system, not including the “output” ddqd. The types of values accepted are the same as described above.
Constraints¶
-
System.
satisfy_constraints
(tolerance=1e-10, verbose=False, keep_kinematic=False, constant_q_list=None)¶ Modify the current configuration to satisfy the system constraints. Letting \(q_0\) be the system’s current configuration, this performs the optimization:
\[q = \arg\min_q |q-q_0|^2 \quad \mathrm{s.t.}\quad h(q) = 0\]The new configuration will be set in the system and returned. If the optimization fails, a
StandardError
exception is raised.Setting verbose to
True
will make the optimization print out information to the console while it is running.Setting keep_kinematic to
True
will modify above optimization to optimize over \(q_d\) instead of \(q\); thus all kinematic configuration variables will be kept constant. Passing a list (or tuple) of configurations to constant_q_list will define an arbitrary set of configurations to hold constant during optimization. Internally this method usesSystem.get_config()
thus, any valid identifier mentioned in Finding Specific Components section will work.
-
System.
minimize_potential_energy
(tolerance=1e-10, verbose=False, keep_kinematic=False, constant_q_list=None)¶ Modify the current configuration to satisfy the system constraints while also minimizing potential energy. The system velocity is set to zero, and thus the kinetic energy is zero. Therefore, the actual optimization that is solved is given by
\[q = \arg\min_q \; (-L(q,\dot{q}=0))\quad \mathrm{s.t.}\quad h(q) = 0\]The new configuration will be set in the system and returned. If the optimization fails, a
StandardError
exception is raised. This function may be useful for finding nearby equilibrium points that satisfy the system constraints.Setting verbose to
True
will make the optimization print out information to the console while it is running.Setting keep_kinematic to
True
will modify above optimization to optimize over \(q_d\) instead of \(q\); thus all kinematic configuration variables will be kept constant. Passing a list (or tuple) of configurations to constant_q_list will define an arbitrary set of configurations to hold constant during optimization. Internally this method usesSystem.get_config()
thus, any valid identifier mentioned in Finding Specific Components section will work.
Lagrangian Calculations¶
-
System.
total_energy
()¶ Calculate the total energy in the current state.
-
System.
L
()¶ -
System.
L_dq
(q1)¶ -
System.
L_dqdq
(q1, q2)¶ -
System.
L_dqdqdq
(q1, q2, q3)¶ -
System.
L_ddq
(dq1)¶ -
System.
L_ddqdq
(dq1, q2)¶ -
System.
L_ddqdqdq
(dq1, q2, q3)¶ -
System.
L_ddqdqdqdq
(dq1, q2, q3, q4)¶ -
System.
L_ddqddq
(dq1, dq2)¶ -
System.
L_ddqddqdq
(dq1, dq2, q3)¶ -
System.
L_ddqddqdqdq
(dq1, dq2, q3, q4)¶ Calculate the Lagrangian or it’s derivatives at the current state. When calculating the Lagrangian derivatives, you must specify a variable to take the derivative with respect to.
Calculating \(\deriv[L]{q_0}\):
>>> q0 = system.configs[0] >>> system.L_dq(q0)
Calculating \(\derivII[L]{q_0}{\dq_1}\):
>>> q0 = system.configs[0] >>> q1 = system.configs[1] >>> system.L_ddqdq(q1, q0)
Calculating \(\derivII[L]{\dq_0}{\dq_1}\):
>>> q0 = system.configs[0] >>> q1 = system.configs[1] >>> system.L_ddqddq(q1, q0) # Mixed partials always commute, so we could also do: >>> system.L_ddqddq(q0, q1)
Calculate an entire derivative \(\deriv[L]{q}\):
>>> [system.L_dq(q) for q in system.configs]
Dynamics¶
-
System.
f
(q=None)¶ Calculate the dynamics at the current state, \(\ddq_d = f(q,\dq,\ddq_k, u, t)\).
This calculates the second derivative of the dynamic configuration variables. The results are also written to
Config.ddq
.If q is
None
, the entire vector \(\ddq_d\) is returned. The array is in the same order asSystem.dyn_configs
.If q is specified, it must be a dynamic configuration variable, and it’s second derivative will be returned. This could also be accessed as
q.ddq
.Once the dynamics are calculated, the results are saved until the system’s state changes, so repeated calls will not keep repeating work.
-
System.
f_dq
(q=None, q1=None)¶ -
System.
f_ddq
(q=None, dq1=None)¶ -
System.
f_dddk
(q=None, k1=None)¶ -
System.
f_du
(q=None, u1=None)¶ Calculate the first derivative of the dynamics with respect to the configuration, the configuration velocity, the kinematic acceleration, or the force inputs.
If both parameters are
None
, the entire first derivative is returned as anumpy
array with the derivatives across the rows.If any parameters are specified, they must be appropriate objects, and the function will return the specific information requested.
Calculating \(\deriv[f]{q_2}\):
>>> dq = system.configs[2] >>> system.f_dq(q1=dq) array([-0.076, -0.018, -0.03 , ..., 0.337, -0.098, 0.562])
Calculating \(\deriv[f]{\dq}\):
>>> system.f_ddq() array([[-0.001, -0. , -0. , ..., 0.001, -0.001, 0. ], [-0. , -0.002, -0. , ..., -0. , -0. , 0. ], [-0. , -0. , -0. , ..., 0. , -0. , 0. ], ..., [-0. , -0.001, -0. , ..., -0.003, -0. , 0. ], [ 0.001, -0. , 0. , ..., -0.005, 0.008, -0. ], [-0.001, -0. , -0. , ..., 0.008, -0.027, 0. ]]) >>> system.f_ddq().shape (22, 23)
Calculating \(\deriv[\ddq_4]{\ddq_{k0}}\):
>>> qk = system.kin_configs[0] >>> q = system.dyn_configs[4] >>> system.f_dddk(q, qk) -0.31036045452513322
The first call to any of these functions will calculate and cache the entire first derivative. Once calculated, subsequent calls will not recalculate the derivatives until the system’s state is changed.
-
System.
f_dqdq
(q=None, q1=None, q2=None)¶ -
System.
f_ddqdq
(q=None, dq1=None, q2=None)¶ -
System.
f_ddqddq
(q=None, dq1=None, dq2=None)¶ -
System.
f_dddkdq
(q=None, k1=None, q2=None)¶ -
System.
f_dudq
(q=None, u1=None, q2=None)¶ -
System.
f_duddq
(q=None, u1=None, dq2=None)¶ -
System.
f_dudu
(q=None, u1=None, u2=None)¶ Calculate second derivatives of the dynamics.
If no parameters specified, the entire second derivative is returned as a
numpy
array. The returned arrays are indexed with the two derivatives variables as the first two dimensions and the dynamic acceleration as the last dimension. In other words, calling:system.f_dqdq(q, q1, q2)
is equivalent to:
result = system.f_dqdq() result[q1.index, q2.index, q.index]
If any parameters are specified, they must be appropriate objects, and the function will return the specific information requested. For example:
system.f_dqdq(q2=q2)
is equivalent to:
result = system.f_dqdq() result[:, q2.index, :]
The second derivatives are indexed opposite from the first derivatives because they are generally multiplied by an adjoint variable in practice. For example, the quantity:
\[z^T \derivII[f]{q}{\dq}\]can be calculated as:
numpy.inner(system.f_dqddq(), z)
without having to do a transpose or specify a specific axis.
The first call to any of these functions will calculate and cache the entire second derivative. Once calculated, subsequent calls will not recalculate the derivatives until the system’s state is changed.
Constraint Forces¶
-
System.
lambda_
(constraint=None)¶
-
System.
lambda_dq
(constraint=None, q1=None)¶ -
System.
lambda_ddq
(constraint=None, dq1=None)¶ -
System.
lambda_dddk
(constraint=None, k1=None)¶ -
System.
lambda_du
(constraint=None, u1=None)¶
-
System.
lambda_dqdq
(constraint=None, q1=None, q2=None)¶ -
System.
lambda_ddqdq
(constraint=None, dq1=None, q2=None)¶ -
System.
lambda_ddqddq
(constraint=None, dq1=None, dq2=None)¶ -
System.
lambda_dddkdq
(constraint=None, k1=None, q2=None)¶ -
System.
lambda_dudq
(constraint=None, u1=None, q2=None)¶ -
System.
lambda_duddq
(constraint=None, u1=None, dq2=None)¶ -
System.
lambda_dudu
(constraint=None, u1=None, u2=None)¶ These are functions for calculating the value of the constraint force vector \(\lambda\) and its derivatives. They have the same behavior and index orders as the dynamics functions.
Derivative Testing¶
-
System.
test_derivative_dq
(func, func_dq, delta=1e-6, tolerance=1e-7, verbose=False, test_name='<unnamed>')¶ -
System.
test_derivative_ddq
(func, func_ddq, delta=1e-6, tolerance=1e-7, verbose=False, test_name='<unnamed>')¶ Test the derivative of a function with respect to a configuration variable’s time derivative and its numerical approximation.
func -> Callable taking no arguments and returning float or np.array
- func_ddq -> Callable taking one configuration variable argument
- and returning a float or np.array.
- delta -> perturbation to the current configuration to
- calculate the numeric approximation.
- tolerance -> acceptable difference between the approximation
- and exact value. (|exact - approx| <= tolerance)
verbose -> Boolean indicating if a message should be printed for failures.
- name -> String identifier to print out when reporting messages
- when verbose is true.
Returns False if any tests fail and True otherwise.
Structure Updates¶
Whenever a system is significantly modified, an internal function is
called to make sure everything is consistent and resize the arrays
used for calculating values as needed. You can register a function to
be called after the system has been made consistent using the
add_structure_changed_func()
. This is useful if you building
your own component that needs to be updated whenever the system’s
structure changed (See Damping
for an example).
-
System.
add_structure_changed_func
(function)¶ Register a function to call whenever the system structure changes. This includes adding and removing frames, configuration variables, constraints, potentials, and forces.
Since every addition to the system triggers a structure update, building a large system can cause a long delay. In these cases, it is useful to stop the structure updates until the system is fully constructed and then perform the update once.
Warning
Be sure to remove all holds before performing any calculations with system.
-
System.
hold_structure_changes
()¶ Prevent the system from calling
System._update_structure()
(mostly). This can be called multiple times, butresume_structure_changes()
must be called an equal number of times.
-
System.
resume_structure_changes
()¶ Stop preventing the system from calling
System._update_structure()
. The structure will only be updated once every hold has been removed, so calling this does not guarantee that the structure will be immediately update.