Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class StatesDocument #3902

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open

Conversation

fcanderson
Copy link
Contributor

@fcanderson fcanderson commented Sep 4, 2024

Brief summary of changes

Class StatesDocument is an addition to OpenSim that adds the ability to serialize and deserialize a complete time history of model states to and from a single .ostates file. This class fulfills a roadmap articulated about 10 years ago. See the bottom of StatesTrajectory.h.

Prior to class StateDocument, only the continuous states (e.g., joint coordinates, joint speeds, etc.) were serialized in a systematic manner, leaving discrete states and modeling options unserialized. One of the challenges in serializing discrete states is that their type can vary (e.g., bool, int, double, Vec2, Vec3, etc.) and existing approaches for serializing states (e.g., storage files and time series tables) do not easily support data of mixed types.

Class StatesDocument uses XML (see class SimTK::Xml) as the framework for serializing all state categories and all data types. It also contains serialization date stamps accounting for time zone, contains an annotation note, and supports serialization with a range of output precisions ( 1 to 20 digits), allowing the user to reduce the .ostates file size or achieve lossless de/serialization, .

Minor additions were made to class StatesTrajectory and StatesTrajectoryReporter to support the StatesDocument class. Note, however, that class StatesDocument is build on low-level SimTK classes and the recently enhanced OpenSim::Component class, and does not have any knowledge of the StatesTrajectory or StatesTrajectoryReporter classes.

For details concerning class StatesDocument, consult the Doxygen compliant comments in StatesDocument.h.

Testing I've completed

Class StatesDocument is accompanied by a unit test. See testStatesDocument.cpp. This unit test exercises state serialization in all categories (continuous variables, discrete variables, and modeling options) and in all supported data types (bool, int, double, Vec2, Vec3, Vec4, Vec5, Vec6). All OpenSim unit tests are passing on Windows 10 for a RelWithDebInfo build.

Looking for feedback on...

  • Are modifications to StatesTrajectory and StatesTrajectoryReporter acceptable?
  • Should the unit test include more comprehensive checks of exception handling and a broader range of models?

CHANGELOG.md (choose one)

I have not yet modified the CHANGELOG, thinking it would be wise to defer the update until an initial review was completed. I will do the update as soon as it looks like the review is converging on approval.


This change is Reviewable

fcanderson and others added 30 commits May 27, 2024 09:12
- Modified the names of the Trajectory methods to match the latest names in Component.h.
- Reintroduced the precision argument in SimTK::Xml::Elment::setValueAs<T>.
Added a place holder for the Catch2 unit test for class StatesDocument.

testStatesDocument.cpp is now being incorporated in the build, and the test case runs when "build" executed on the target "RUN_TESTS_PARALLEL".
- Moved all utility methods into a struct with local linkage.
- Streamlined code by adding `getEltValue()`. Use of this method reduced some code repetition.
- Minor changes to line formatting.
- Minor corrections/additions to comments
- Added code borrowed from testComponentInterface.cpp to generate a dummy model with a variety of states.

The code compiles and runs with all test cases passing, but this is just a starting point. De/serialization using class StatesDocument is not yet being tested.
- Removed code borrowed from testComponentInterface.cpp. That code was not the way to go. A legitimate model is needed.

I'm starting over.
- Made small improvements and corrections in the documentation.
- Now building a simple model and running a simulation.
- Added code to serialize, deserialize, and then re-serialize the state trajectory recorded during a simulation.

The code is functioning as expected!
- Changed member variable `m_states` from type std::vector<SimTK::State> to SimTK::Array_<SimTK::State>
- Added `exportToStatesDocument()`
- Added a `get` method that provides access to the underlying SimTK::Array_<SimTK::State> member variable.
- Added a method that tests equality of 2 state trajectories.

The method does not yet test equality for discrete variables or modeling options, but Qs, Us, and Zs are passing.
- Added a static method that computes maximum rounding error based on the specified precision and value that is rounded.
- Cleaned up the computation of max_eps.
- Added equality comparisons for discrete variables and modeling options.
- Added ExtendedPointToPointSpring.
Since the trajectory was changed from std::vector<State> to SimTK::Array_<State>, bounds checking behavior has changed.

SimTK::Array_<T> only checks bounds in DEBUG. In other words, an IndexOutOfRange exception will not be thrown in a release build. To account for this, I inserted an #ifdef to skip bounds checking unless in DEBUG.
Added the following types:
- bool
- int
- double
- Vec2
- Vec3
- Vec4
- Vec5
- Vec6

All tests are passing, and the .ostates file looks right.
So that discrete variables can be updated during a simulation, class ExtendedPointToPointSpring is now built on top of class ExtendedTwoPointLinearSpring. ExtendedTwoPointLinearSpring allocates auto-update discrete variables in its own `realizeTopology()` and those discrete variables are updated in its own `realizePosition()`.
The discrete variables can be added in extendRealizeTopology().
And, they can be changed in computeForce().
- Changed the allocation for discrete variables to the DefaultSystemSubsystem.
- Primarily using the subsystem index instead of the pointer to the subsystem.
- Discrete variables are being changed during simulation in ExtendedPointToPointSpring::computeForce().

All checks are now passing!
The note is serialized and deserialized.

All checks are passing.
1. Now looping over over output precision from precision = 2 to precision = 22.
2. Added the ability to change the name of a discrete state for the purpose of testing exceptions when a discrete state is not found.
fcanderson and others added 12 commits September 3, 2024 23:36
const SimTK::Array_<SimTK::State>& StatesTrajectoryReporter::getStateArray();
- Removed StatesDocument::parseDoc().  No reason to have it.
- Simplified the name of StatesTrajectory::getUnderlyingStateArray() to getStateArray().
- Refined documentation comments and checked them for accuracy.
- Deleted unused code.
- Updated copyright dates.
- Improved documentation comments.
An error was being issued for Ubuntu and Mac builds because "OPENSIM_STATES_DOCUMENT_H_" was not commented out on line 609. I added the comment token.
In a number of instances, I changed the type from `int` to `size_t`.
Windows can handle a std::string, but Mac and Ubuntu builds are failing.
- Addressed some type warnings.
- Removed the method `ComputeMaxRoundingError()`.
@aymanhab
Copy link
Member

aymanhab commented Sep 5, 2024

Thanks @fcanderson Can we fix build failures to start. Thank you

@fcanderson
Copy link
Contributor Author

Hi @aymanhab. Thanks for the reply. Sorry for the long radio silence. I got really busy with the class I'm teaching.
I believe the errors occur only during the wrapping (Java, Python, ...). I'm guessing I need to insert some compiler directives that prevent a wrapping attempt. I may have time to look into that this morning, as long as I don't need to put out any fires.

@aymanhab
Copy link
Member

@fcanderson No problem, it's just an issue of missing default constructor I think, which I can explicitly communicate to SWIG but I wasn't sure if that's the intended behavior. Let me know if you get busy and I can investigate further. Thank you

@fcanderson
Copy link
Contributor Author

I'll create a default constructor. It won't be useable for anything meaningful, but it might make the errors go away.

fcanderson and others added 3 commits September 25, 2024 11:25
I temporarily added a default constructor to see if that would resolve some errors that are occurring in the Continuous Integration.
It looks like different return types of size() methods are assumed across operating systems. I inserted a (size_t) cast to address compiler errors on Ubuntu
@fcanderson
Copy link
Contributor Author

fcanderson commented Sep 25, 2024

@aymanhab I added a default constructor. Continuous Integration now performs the wrapping successfully. At least the build/testing is getting farther.

The two tests that fail are:
132 - python_tests (Failed)
133 - python_examples (Failed)

I haven't been doing the python wrapping on my desktop. I will look into doing that.
Do you have time to take a look to see if there is a quick fix? If not, I'll do some digging.

@aymanhab
Copy link
Member

No worries @fcanderson I'll take a look and let you know if I have questions. Thank you 👍

@fcanderson
Copy link
Contributor Author

Ok! Thanks @aymanhab.

@aymanhab aymanhab changed the base branch from main to ostates_merge September 26, 2024 22:14
@aymanhab aymanhab changed the base branch from ostates_merge to main September 26, 2024 22:24
@aymanhab
Copy link
Member

Failure details:

ERROR: test_index_and_iterator (test_states_trajectory.TestStatesTrajectory)

Traceback (most recent call last):
File "D:\dev\src\opensim-core\Bindings\Python\tests\test_states_trajectory.py", line 45, in test_index_and_iterator
model.calcMassCenterPosition(state)
File "D:\dev\build\opensim-core\Bindings\Python\Release\opensim\simulation.py", line 25970, in calcMassCenterPosition
return _simulation.Model_calcMassCenterPosition(self, s)
ValueError: invalid null reference in method 'Model_calcMassCenterPosition', argument 2 of type 'SimTK::State const &'

@aymanhab
Copy link
Member

@fcanderson The error happens while iterating through the stateTrajectory due to the change from std::vector into SimTK::Array in
stateTrajectory.h is there a reason for the change? std::vector iterators are likely better supported by swig out of the box than our home grown SimTK::Array, but if the change is necessary I'll dig deeper into the iterator across bindings. Thank you

@aymanhab
Copy link
Member

swig/python detected a memory leak of type 'SimTK::Array_< SimTK::State,unsigned int >::const_iterator *', no destructor found.
swig/python detected a memory leak of type 'SimTK::Array_< SimTK::State,unsigned int >::const_iterator *', no destructor found.

@fcanderson
Copy link
Contributor Author

@aymanhab Thanks for tracking the origin of this error down.

The reason for the transition is that Simbody uses SimTK::Array_ for its underlying std::vector-like functionality. The original rationale (I assume coming from Sherm) was for some speed and for binary compatibility. The OpenSim::StatesDocument class uses SimTK::Array_ to be compatible with Simbody, as do a number of newly available trajectory methods in OpenSim::Component.

I get why swig would be more likely to handle std::vector properly than SimTK::Array_. One option might be to hide the underlying implementation (vector vs. Array_) so that python / java / etc. don't see it. For example, by just using an index instead of an iterator -- a little slower sometimes, but I don't think noticeably.

I will look into this issue and see if I can come a good/easy solution.

@aymanhab
Copy link
Member

aymanhab commented Oct 1, 2024

Thanks @fcanderson My first inclination would be to restore the stateTrajectory class to use std::vector for backward compatibility of client code, so that iterators (which are used by the stateTrajectory class clients) are maintained. The speed up in SimTK::Array_ may be important for small arrays internal to simbody (where space efficiency matters) but std::vector is used extensively elsewhere and the State object is massive enough that overhead would be negligible I'd think. I can try that myself if you agree so please let me know..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants