-
2015-Apr-24 - Is this a spacecraft?
04/05/2017 at 17:13 • 0 commentshttps://stkwans.blogspot.com/2015/04/is-this-spacecraft.html
This is not a pipe. (It is a picture of a pipe) Is this a picture of a spacecraft? I have discussed my rocketometer project on this blog in depth before. That's it, the purple board in the picture above. I think I can lay claim to it being a full-blown spacecraft.
- It has structure.
- It has command/data handling.
- It has a science payload with multiple instruments.
- It has independent power - it is charged from the rocket payload, but carries its own battery and is capable of operating for the whole mission from launch through recovery. In fact, it did operate on its own power during entry and descent. From the point of view of the Rocketometer, the external power system is ground support equipment.
- It is capable of operating in the vacuum and freefall of space. If it were magically transported into orbit, it would operate for at least a little while (it lacks thermal control).
- It lacks propulsion and recover, but so do many spacecraft.
The biggest strike against it is that it was bolted inside another spacecraft. How many other systems in the rocket would I count as a spacecraft under this same definition?
The other biggest strike is that it has no telecom system. I know of spacecraft which have been flown with transmitters but no receivers, but the only spacecraft I can think of that had neither were things like Echo, Lageos, and Starshine. Do those count as spacecraft?
-
2014-Mar-10 - Rocketometer raw data published
04/05/2017 at 17:11 • 0 commentshttps://stkwans.blogspot.com/2014/03/rocketometer-flight-data-published.html
This documents the data produced by the Rocketometer during NASA Sounding Rocket Flight 36.290, 2013 Oct 21.
I was getting sick of looking at this data, unable to fully process it but holding it jealously. This has to change. The data wants to be free. Get it at https://drive.google.com/#folders/0B_jwoDhgAF3cNXo0OG5MS2RoM3c
Encoding format
The data was captured and encoded in the form of CCSDS packets. The exact structure of each packet is most completely documented in the source code.
Source code
The Rocketometer contains an LPC2148 ARM-based MCU with 512kiB flash memory. As this was far more memory than I needed for the firmware, I worked out a way to attach the source code of the firmware as well as the hardware design to the firmware itself. The Rocketometer spits this out into the packet stream as one of the first things it does at startup. How's that for configuration control?
The source code package is in the form of a ZPAQ TAR archive. The folder includes all the C++ source code, Makefiles, and such for rebuilding the firmware, which will be identical to the firmware carried on the flight with the exception of the date stamp on the firmware.
The hardware design is in the form of an Eagle CAD board and schematic, editable with the Eagle freeware version (as that is what it was designed with). It also includes a bill of materials in the form of Digikey catalog numbers for all parts used on the board.
The source code package is attached to the firmware, along with pointers to exactly where in the firmware the package begins and ends. This enables the firmware to copy a piece of itself into the packet stream as multiple CCSDS packets.
Time Stamp
The firmware uses one of the MCU counter/timers for timing. This counter is a 32-bit unsigned number which starts at zero during firmware bootup, and counts up to one less than 3.6 billion counts exactly, at which point it resets to zero. The counter nominally runs at 60MHz, so this counter resets once every 60 seconds. Each time-sensitive packet (almost all of them except for some startup packets) have this count in it, and the packets are generated at such a rate that it is possible to unambiguously unwind the counter and get an exact number of counts since startup. Unfortunately the time of startup was not marked in any absolute time scale such as UTC, so absolute timing has to be inferred from timestamped events in the data.
Sensor data
The sensor data stream consists of three separate streams of packets:
- The Fast packet includes the MPU6050 3-axis acceleration, 3-axis rotation, and temperature readings, along with the ADXL377 3-axis high-acceleration readings. These were read once every 3 milliseconds and timestamped as indicated above.
- The Compass packet includes the HMC5883 3-axis magnetic readouts. These were measured at a multiple of the Fast packet period (see the source code for details)
- The BMP packet includes the BMP180 pressure and temperature readouts, both raw and calibrated into physical units.
- The Power packet stream includes one packet each time the external power state changed (including one at startup).
- The Vertical packet stream includes data on the vertical sensing fast/slow packet rate (see the source code for details). I have verified that this worked as intended, and that the entire flight was recorded at the fast rate.
Packet storage
Packet storage was on a 16GB microSD card. Less than 1GB was used between the time I delivered the Rocketometer and the time it was returned to me.
Power
The board consumes something around 100mA, not counting charging. The charger can use and additional 100mA. The system has an internal battery in the form of a 100mAh LiPo cell. The Rocketometer is powered externally by a 5V line from the rocket payload. It charges the battery with its on-board LiPo charging circuit as necessary when external power is available, and runs off the battery when external power is off. The on-board switch is not accessable, so the board is on whenever the rest of the rocket payload is on, and only turns off once its battery discharges. The battery is made safe by its standard integrated safety circuit and the dedicated LiPo charging circuit.
The Flight
The Rocketometer was integrated with the rest of the rocket payload months before the flight. After that time, no human interaction whatsoever took place. It was returned to me for readout a couple of hours after the flight was completed. In that time, it recorded 65 files, one for each time it was power-cycled. The one containing the flight was file RKTO0620.SDS . This file contains 54.4 MB of data covering well over an hour, from about 35 minutes before the flight until well after impact. External power was cut 585 seconds after launch, and the on-board battery sustained it for the rest of the flight.
Rail data
The file RKTO0620.SDS starts after the rail was raised to almost vertical. Other records were taken when the rail was horizontal, in particular RKTO0600.SDS. This data has been processed into the same tables and published. Interesting things such as orientation of the Rocketometer relative to the rail can be determined from that.
It would have been nice for the Rocketometer to have been recording while the rail was raised from horizontal to vertical. Unfortunately this did not happen. Maybe next time...
For whatever reason, it seems that no magnetic data was recorded while the rail was horizontal. Apparently magnetic data is not recorded when the board is recording at low rate.
Rocketometer coordinate system
All sensors were co-aligned, so each individual sensor matches the whole Rocketometer frame. The Rocketometer was mounted close to the spin axis of the rocket, with the +Z axis pointed towards the tail. Consequently the thrust acceleration was primarily read out as a -Z acceleration. It should be parallel to that axis to within normal mechanical tolerances, almost certainly quite a bit better than 1°.
Based on the rail data, the +X axis of the Rocketometer was pointed almost straight down when the rocket was hanging horizontally from the rail.
In the flight after burnout but before spindown, the rocket was coasting well above the atmosphere and spinning with constant angular momentum, nearly around its long axis. Therefore the entire sensed accleration is pure centripetal. Therefore, the distance and direction from the accelerometer to the spin axis can be measured. I did this once, but I wasn't sure about the signs and therefore didn't record it.
Processing into tables
The packet stream format is not convenient or quick for import into IDL. For this purpose, a packet dumper, extractFast, was written in pure C which reads a Rocketometer data file, parses out each kind of packet, and writes each different apid into a different file. All the fast packets are together, all the slow packets, all the compass, all the source code, etc are each in their own file. Since the most common case is that each apid has a consistent length, this apid file can be read quickly into IDL by a read_binary, treating it as a 2D array of 16-bit integers, where rows are individual packets, and columns are fields in the packet.
Each packet is assigned a global sequence number, and this number is one of the things written to the APID file. With this sequence number, the order of different packets in different files can be determined.
These APID files are then imported into IDL, where the timestamps are normalized, values are converted in endian-ness as needed, and in general the packets are parsed. As this is an array operation, it can be done several orders of magnitude faster than parsing each packet individually in IDL.
The published tables have the timestamp normalized (cycle ambiguity fixed, converted into seconds, offset from power-on to launch subtracted) but otherwise the data is as recorded by the Rocketometer in flight. In particular, no translation from raw DNs to physical units is performed.
Included files
- RKTO0620.SDS.zip - raw flight data file, PKZIP compressed
- RKTO0600.SDS.zip - raw rail data file, PKZIP compressed
- firmware.tar.gz - Firmware package, as flown (from RKTO0620.SDS) but zpaq uncompressed and gzip recompressed
- extractFast.c - C program described above
- apid list.xlsx - Some documentation of the packet format, not know to be completely up-to-date. Use the source code as the final authority.
- import_fast.pro - IDL program which converts extractFast output to CSV tables, among other things.
- RKTO0620_csv.zip - Compressed archive containing the following files
- RKTO0620_fast.csv - Comma separated values, fast packets as described above
- RKTO0620_bmp.csv - Comma separated values, BMP180 temperature and pressure readings, raw and calibrated into physical units by the Rocketometer in-flight
- RKTO0620_hmc.csv - Comma separated values, HMC5883 magnetic readings, as described above.
- RKTO0600_csv.zip - Compressed archive containing the following files
- RKTO0600_fast.csv - Comma separated values, fast packets as described above
- RKTO0600_bmp.csv - Comma separated values, BMP180 temperature and pressure readings, raw and calibrated into physical units by the Rocketometer on-board
-
2013-Oct-25 - On-board video
04/05/2017 at 17:04 • 0 commentshttps://stkwans.blogspot.com/2013/10/rocket-flight-36290.html
This video shows the raw output of the two cameras on the rocket (the forward-looking camera footage is replaced by the more interesting exterior views during the launch) as well as three "instruments". The first one is a clock with a minute and second hand. The second is a velocity and acceleration gauge. The velocity is that reported by the radar data from the flight -- the rocket doesn't carry a GPS, as the range radar is more accurate. The acceleration is that reported by the Rocketometer. It shows 1G while sitting on the ground, even though the velocity is not changing at all, and it shows 0G in space, even though the velocity is changing by almost 1G as the rocket falls back towards the Earth. This is the same acceleration as would be felt by a hypothetical passenger on the rocket. The third instrument shows the flight track, and has an altitude readout. This altitude is again from the radar tracking data.
The highest acceleration during the boost was just over 12G, while the highest acceleration during the entire flight was during reentry, where the acceleration hit 21G and briefly saturated the low-acc sensor. The high-acc sensor recorded data the whole time, and caught the part of the flight that the low-acc sensor missed.
-
2013-Oct-23 - Rocketometer flight experience
04/05/2017 at 16:58 • 0 commentsThe Rocketometer flew into space on 21 October 2013, on board NASA sounding rocket 36.290. The following are notes on its performance, including any glitches or other things to be corrected on the next flight
General
The Rocketometer performed as designed during the flight. It was able to record inertial readings once every 3ms throughout the flight. I have to look pretty hard to find anything which didn't go according to plan.
The device was installed with -Z in the direction of powered flight. This was unexpected, but caused no problems. It's a good thing I wrote the vertical test to check for both directions, no Genesis sensor problem for me.
Card reading
I tried reading the card in my laptop Natsumi, in Linux mode. In order to insert it there, I put it in a microSD to normal SD adapter, with the write-protect switch on the adapter set to lock. The card failed to read in this mode. I then put the switch in unlock, and initially it didn't read that way either. It seemed like it either just took a long time to mount, or that the video-copying I was doing simultaneously was interfering with it. Anyway, I eventually saw it as /dev/sdc1, started doing a dd dump, and about half way through that, the device automounted and I could copy files normally.
Magnetometer
The magnetometer was able to see the Earth's magnetic field through the skin of the rocket and the various cases and boxes between it and the external field.
Axis namingI had assumed without looking closely that the sensor registers in the HMC5883 were in order, and wrote the code accordingly. Upon getting the data, the "x" and "z" axes were showing spin, when I expected the x and y axes to show the spin around the Z axis. Closer examination of the HMC5883 data sheet shows that the registers are in XZY order, not the expected XYZ. In order to maintain compatibility, the magnetometer packet is now documented to be in XZY order and will not be changed. Code is changed such that it catches the Y and Z axes in the Y and Z variables, in case any on-board systems wish to analyze the data. Any change in the packet to write XYZ ordered data will be done with a new APID.
TimingThe device is capable of readout at 100Hz or more. Design performance was one compass read every 20 inertial reads. Actual performance achieved that, with a read every 60ms, except one sample in every 7 was delayed, only reading after 111ms. This has to do with the fact that the compass and pressure sensor were not in phase with each other. The code as originally designed had a phase variable incremented once every inertial read. It was checked (phase%20==0) for the appropriate phase to fire the compass reading, then checked and reset whenever the pressure sensor was read. This happened at such a rate that effectively every 7th reading was delayed, since the phase got reset at the wrong time.
Future code will have an independent phase for the compass and pressure sensors, and will read the sensor closer to its capability.
Hard and soft ironThe hard iron effect was of such a magnitude that the sensor readout ellipsoid did not even contain the zero vector. The hard iron offset appeared to change in a step on at least one axis during reentry, near 575 seconds after launch. There is significant soft-iron distortion of the ellipse as well.
Analyzing hard and soft iron is roughly equivalent to finding the center and size of an ellipsoid that fits the measured points. We use the ellipsoid_fit routine which finds the principal axis unit vectors, radii, and center of the best-fit ellipsoid. The center is directly the hard iron distortion in DN. The principal axis unit vectors and radii are used as eigenvalues and eigenvectors, and the matrix which has those eigenproperties is found as follows:
[A]x⃗ =λx⃗ [A]x→=λx→
[A][x⃗ 0x⃗ 1x⃗ 2]=[λ0x⃗ 0λ1x⃗ 1λ2x⃗ 2][A][x→0x→1x→2]=[λ0x→0λ1x→1λ2x→2]
[B]=[λ0x⃗ 0λ1x⃗ 1λ2x⃗ 2][B]=[λ0x→0λ1x→1λ2x→2]
[X]=[x⃗ 0x⃗ 1x⃗ 2][X]=[x→0x→1x→2]
[A][X]=[B][A][X]=[B]
[A]=[B][X]−1[A]=[B][X]−1
This matrix [A][A] directly converts a unit vector magnetic field into a point on the ellipsoid, and it does so in a manner without rotating the ellipsoid, because of the eigenvectors. The eigenvectors are scaled straight out to the ellipsoid, with no rotation.
Since the field strength changed during the flight as a result of altitude change, all the measurements don't really fall on the same ellipsoid, but one which varies with field strength. We will ignore this. The Kalman filter will carefully run the WMM2010 code to get the field BIBI in nanoTeslas, then convert to DN by multiplying by the A matrix above:
B⃗ body=q′rbB⃗ IqrbB→body=qrb′B→Iqrb
B⃗ DN=[A]B⃗ body+B⃗ hard,DNB→DN=[A]B→body+B→hard,DN
The measured hard and soft iron during the flight between t=0t=0 and t=575t=575 is then:
B⃗ hard,DN=⎡⎣⎢⎢843.15455−1139.8037603.84026⎤⎦⎥⎥B→hard,DN=[843.15455−1139.8037603.84026]
[A]=⎡⎣⎢⎢12567.9128058990−1062.4357595761139.1144904110−1062.435759576110553.3281864438420.3887030047139.1144904110420.38870300477970.6607032257⎤⎦⎥⎥10−6[A]=[12567.9128058990−1062.4357595761139.1144904110−1062.435759576110553.3281864438420.3887030047139.1144904110420.38870300477970.6607032257]10−6
RGB Light and blinklock
This is one that I was worried about before the flight. The RGB light is blinked in order to show that it is working - one of the colors is turned on when a sector is written to the card, then back off when the next sector is written, then the next light is on with the next sector, etc. At high rate recording, the light blinked faster than a human could count, while at low rate, it blinked at about half-second on, half second off.
If there was an error, the device went into blinklock. This blinked out an error code, with a red light representing a zero bit of the error code, and a green light a one bit. A blue light indicated the end of the number. This is a problem because error code 2 looks like a slow blink rate for the low rate recording. The blink lock is slower, but hard to recognize if you aren't used to seeing both. Further, it was a blink lock, which meant that the device would do that forever without attempting to reset itself.
Next mission, the light will blink red for errors, with a zero bit indicated by a 1-second light and a one bit by a 2-second light. Blue light again for end of code. The normal write pattern will not blink the red light. This way, if you see green, things are good. If you see red, things are bad. The device will blink the error code out twice then reset itself.
Timing
The 60MHz clock is used as the nominal time reference. This is converted and wrapped using the fix_tc function to provide a time for each packet relative to the restart of the device. According to that time scale, the last undisturbed accelerometer sample before the flight was the last sample before 2019.0 seconds exactly, and the next sample was after that time, so launch time on the device clock is considered to be 2019.0s exactly.
Several sharp events were seen in the inertial data, such as detach of stages and other parts. Also, the time of external power disconnect was recorded by the device. Correlation of these visible sharp events with the actual mission timeline will provide correlation of the device clock with UTC. In particular, it looks like the board was powered up 2019 seconds before flight (thus t0 is this value), then had power cut at 1139.629s before flight. The device ran on internal power until 852.622s before t0 and was cut off again in flight (as expected) at 585.445676 seconds after t0.
Touchdown occurred at 933.943 seconds after first motion, as reported by the low-scale accelerometer. Touchdown was rather gentle, never saturating the low-scale measurement. The payload had fallen over completely by 935.314s, and came to complete rest at about 941.4s
Inertial sensor calibration
The inertial sensors experienced a substantial time in weightlessness, from about 130 to 470 seconds after launch. During this time, the mean rotation speed of the payload was one revolution per year since it was pointed at the sun. This was around ecliptic north, but the payload Z axis was perpendicular to this and the mean rotation speed around that axis was precisely zero.
Weightless values and temperatureThe temperature of the sensor went from ~700DN to ~1300DN during the weightless portion, in a linear fashion. It is basically impossible to distinguish linear trends vs time from linear trends vs temperature under these conditions. However, the part includes a temperature sensor for a reason, so we will presume that the weightless value is a function of temperature.
Axis Mean offset mean d/dT stdev (no temp comp) stdev (with temp comp) max 54.7954 0.000779873 3.49191 3.49004 may 3.28361 -0.000486003 3.27171 3.27099 maz -85.9718 -0.00460784 4.62528 4.57393 mgx -50.2815 -0.00114513 0.657327 0.634269 mgy -6.18138 -0.000234544 0.696301 0.695410 mgz 6.47691 -9.95948e-005 1.19776 1.19765 hx 2054.43 0.730475 hy 2058.93 0.918055 hz 2050.26 1.04895 Consider that the slopes have to be multiplied by the ~1000DN temperature range that the part saw. MAZ varies by 4DN over that much range, MGX by 1DN, and all the rest are less than 1DN over that range.
In a very real sense, the majority of the calibration of the Rollercoasterometer by the Rocketometer is accomplished with this table. This is over 50% of why I put a Rollercoasterometer into space.
Initial conditions
The launcher is at 106.320160°W, 32.417995°N, 1209m above the WGS-84 ellipsoid. The initial velocity is moving with the surface of the Earth. The initial orientation is determined with the accelerometer and compass, except for the problem of significant distortion of the compass reading by the steel in the launcher. The accleration used to determine local vertical must be taken when the vehicle is not moving. Examination of the compass data shows that the external distortion is gone by 1.0 seconds after launch, while the vehicle has rotated very little (less than 2°) by this point. Therefore we can use point-toward, pointing the sensed acceleration at the local gravity vector before launch, and the sensed magnetic field toward the modeled field from WMM2010 1 second after launch, presuming that the vehicle orientation is the same in both instances.
The sensed magnetic field 0.974456s after launch is
B⃗ b=⎡⎣⎢⎢28564.7076367.164740336.593⎤⎦⎥⎥B→b=[28564.7076367.164740336.593]
There is an approximately 10° mismatch between the field reported by the compass and the launch elevation and azimuth reported in the radar data. So, what we are going to do is run the filter with only the radar data to determine position and velocity, and using the compass and gyro sensors from the flight only -- no acceleration in this pass.
-
2013-Oct-21 - IT WORKED!!!
04/05/2017 at 16:53 • 0 commentsI turned the Rocketometer over to the rocket guys back in July, and had no contact with it until after the flight today. It had been exposed to every environment it was going to see except for vacuum, but I still had no confidence that it would work.
Well, it did.
The first time I stuck the SD card into my computer after the flight, it said "what card?"
That was disappointing.
It took a few minutes for the computer to recognize the card, but when it did, I saw that it had recorded 419 MiB in 66 files. One of the last was also the longest, so I ran it through my quick extraction program, then through IDL, and saw the characteristic acceleration pulse during first and second stage.
The first thing I did after that was press the Victory button on my phone:
No one else in the lab either heard that or got it, so I had to shout, "It Worked!!!".Now I have to analyze the data... -
2013-Oct-21 - Public Relations
04/05/2017 at 16:51 • 0 commentsI had worked on sounding rocket flights before (this was the fifth or so flight of this payload) in an official capacity, but this time I was there on my own time and budget. Therefore I took my own car on the long trek to Las Cruces, New Mexico, the nearest civilization to White Sands.
Even though it was a noon launch, you have to get to the base extra early. I was there around 7:00am, because the safety roadblocks go up about then, and because this is the last time any of us get to actually touch the rocket. A pretty common thing to do is to sign the rocket with a sharpie, and put stickers on it. Unfortunately, I didn't get a chance to sign the rocket (I was a few minutes late) but I did get to put a Rocketometer 1 sticker on the interior of the blockhouse.
This time, our Principal Investigator (PI, the chief scientist and person most responsible for the mission) made an effort to get many people from the lab to join us at the launch. We had about 15 people that were able to attend, and I, being a veteran of past launches but having no official duties on this flight, was drafted for public relations. As I said, it was a several hour wait from the time people had to show up on site to the time of launch. So, I told stories I had directly experienced, stories I had heard about the base, stories about Range Safety, etc.
For instance: The launch was at White Sands Missile Range, Complex 36 Athena launcher. This is a complex run by the US Navy, on a US Army base. The mission was sponsored by NASA, so there was plenty of bureaucracy to go around. The Army was in charge of Range Safety, which meant that there was an Army officer somewhere with his hand on a button ready to set off the Flight Termination System (a fancy word for "self-destruct") if needed. This position dates back all the way to 1946, when the Army started flying captured V2 rockets. The range is a long piece of ground, with the launch sites in the south and the intended impact points in the north. One day, they launched a V2 that went off course and started flying south, ultimately crashing in a cemetery in Juarez, Mexico and causing quite an international incident. On that day, the concept of Range Safety was born. Since that day, no rocket launched on any American rocket range has caused any damage or injury to anyone off the range.
I also talked about the history of Complex 36. It was built by NASA back in the Apollo days, for testing the launch escape system. The whole complex is still like a time capsule of how rocketry was done. I say there is quite a bit of bureaucracy involved in a simple sounding rocket launch, but that is NOTHING like what has to be done to get something into orbit. There is an old saying about how you can't fly until the weight of the paperwork equals the weight of the vehicle. Well, a sounding rocket is a pretty light vehicle, so there is still plenty of opportunity to get hands dirty.
I kept the crowd entertained with stories, with videos, with talks about sounding rockets in general and our mission in particular, until just before noon when we all went outside (about half a mile away from the launcher) and counted down to launch.
If you have never seen a rocket launch, you have no idea what one looks like. Pictures don't do it justice. A rocket is bright. If you have seen ten launches, you still don't remember what one looks like. It is brighter than you remember.
After the launch, we went back inside to listen to the chatter on the voice loops. The main payload worked fine, and the three scientists operating it collected the data they needed in space. After about 10 minutes, the rocket fell back into the atmosphere, slowed from about mach 7 to almost zero in about 20 seconds, then plummeted back to near the ground, when it fired its drogue and then main parachute, bringing it to a soft landing in the desert about 15 minutes after launch.
About an hour after landing, the Army sent a pair of helicopters out to pick up the payload. Three hours after launch, the payload was back in the barn. The data from the main payload was transmitted during the flight, but the Rocketometer had no telemetry transmission at all -- it was just a passenger. It recorded all the data on its own memory card.
-
2013-Oct-21 - Minimum mission success achieved!
04/05/2017 at 15:15 • 0 commentshttps://stkwans.blogspot.com/2013/10/minimum-mission-success-achieved.html
At 12:01:12MDT today, the Rocketometer achieved minimum mission success by riding above 100km and therefore reaching space.
It will be some time yet before I can recover the device to see that it worked, which will represent full mission success.
This was sent from my phone on the day of the flight, at 2:56pm (about 3 hours after the flight, which launched at 12:00 noon). That's why it is so short.
-
2013-Oct-12 - The Curiously Recurring Template Pattern
04/05/2017 at 13:21 • 0 commentshttps://stkwans.blogspot.com/2013/10/the-curiously-recurring-template-pattern.html
Go look up on Wikipedia what it is. I am going to talk about how I am having to use it.
I was doing fine with the Rocketometer analysis code in C++, using the NewMat library to handle matrices, with a Quaternion layer on top that I wrote myself. After five days of doing battle with various things, I finally got something that worked, but I was curious if this was the "best" way to do it. The C++ Standard Template Library didn't have anything directly related to matrices. The Boost library had a section called uBLAS, but the documentation for it kind of de-recommended itself. It suggested several alternatives, and the one that looked best is called Eigen.
Eigen is interesting in that it is entirely header files, containing almost all of its code in C++ templates. Templates are cool, mostly because when they are instantiated, the compiler gets to see the code in the context that it is used, and gets to inline and optimize it there. Specifically, Eigen contains a dynamic-sized matrix, but also is a template for fixed-sized vectors and matrices. I want to use these as much as possible because all vector sizes used in Rocketometer data analysis are known at compile-time, so the compiler can unroll loops and so on to best optimize the code.
However, templates do not mix with virtual methods, so I had to figure out how to make that work, since I used virtual methods to implement the physics model. I had code that looks like this with NewMat:
class SimModel { protected: /** Physics function. Calculates the derivative of the state with respect to time */ virtual ColumnVector fd_only(double t, const ColumnVector& x)=0; /** Measurement function. Calculates the measurement from the given state and time */ virtual ColumnVector g_only (double t, const ColumnVector& x, int k)=0; /** Physics function with process noise. Uses fd virtual function to calculate physics, then adds process noise.*/ ColumnVector fd(double t, const ColumnVector& x, const ColumnVector* v); public: /** Measurement function with measurement noise. Uses g virtual function to calculate measurement, then adds measurement noise. */ ColumnVector g (double t, const ColumnVector& x, int k, const ColumnVector* w) { ColumnVector result=g_only(t,x,k); if(w)result+=*w; return result; }; };
But I wanted to adapt that to use Eigen, specifically with the fixed-length vectors, since the size of the state vector is determined by the problem and known at compile time. That means that ColumnVector has to go, to be replaced by Matrix<double,n,1> where n is a template parameter determining the size of the state vector. But what about the measurement? The purpose of the k parameter to g_only is to tell which of several kinds of measurements to use. For instance, in the Rocketometer problem, we have a measurement vector coming from the inertial and magnetic sensors, treated as a single 9-element vector. We also have measurements coming from radar or equivalent, treated as a 3-element vector. So, we need a template function g_only, which generates either a 9-element vector or a 3-element vector. You can't do that and have it be virtual, too. Basically, virtual functions are a runtime binding issue, while templates are a compile-time binding. So, I can't have a virtual g_only function, callable by the base class g function.
Enter the Curiously Repeating Template Pattern (CRTP). As it happens, this is something that I read about just a few days ago, just reading up on the C++ language in general. For us, the pattern goes something like this:
template<int n, class Derived> class SimModel { public: template<int m> Matrix<double,m,1> g (double t, const Matrix<double,n,1>& x, int k, const Matrix<double,m,1>* w) { Matrix<double,m,1> result=static_cast<Derived*>(this)->template g_only<m>(t,x,k); if(w)result+=*w; return result; }; };
Note that g_only isn't even defined in this class template, only used. In fact, one of the weaknesses of CRTP is that it implies definitions without expressing them, so it is hard to document. Also note that extra template keyword there after the arrow operator. See here for details.
The derived class then looks like this:
template<int n> class RocketometerModel: public SimModel<n,class RocketometerModel<n>> { template<int m> Matrix<double,m,1> g_only(double t, const Matrix<double,n,1>& x, int k); };
So what happens is that the compiler
- Parses the template for SimModel, but doesn't compile it, because it's a template, not actual code yet. Therefore it doesn't matter that g_only is undefined yet.
- Parses the template for RocketometerModel, and again doesn't compile it.
- Parses the main code, compiling as it goes along until it hits RocketometerModel. It instantiates and compiles RocketometerModel, in the process instantiating and compiling SimModel.
- When SimModel is being instantiated and compiled, it has a call to RocketometerModel g_only, but this is ok, since that is available already, since step 2 has already happened.
Now the derived class might not be a template, it might be an actual class. In this case, the base class is instantiated and compiled with the derived class. In either case, everything is available before it is used, even though the code might look otherwise.
Now this part I will write in bold, so that Google can see it. The other curiously repeating template pattern is having to use the word .template when using (not defining) a template member function. This solves the error invalid operands of types ‘<unresolved overloaded function type>’ and ‘int’ to binary ‘operator<’ .
It appears that there is a weakness in the C++ operator precedence table, such that in certain cases it can't distinguish the opening angle bracket of a template parameter set from a normal compare-for-less-than. In order to disambiguate, you throw in the word template after the dot (it will also work after a pointer arrow -> if you are using one of those). I don't understand it completely myself, but Eigen uses this itself, which is how I found out about how it works in the first place.
I am gradually coming to the conclusion that Java did it right, with generics which are real compiled code. Java generics are enabled by the "everything is an object" model in which all objects descend from a common class. I am also beginning to think that Java did it right with operator overloading. Operator overloading, even when fully appropriate, like defining * to mean matrix or quaternion multiplication, is fun to use but a nightmare to implement. And, if it is implemented wrong, it might be left to the user to find out when he tries to do something the implementer did not foresee.
All in all, I give Eigen 4/5, especially recommended for new projects, but not for converting old projects. The biggest advantage is speed. What took IDL over an hour, took Java and C++ with NewMat about 4 minutes, but takes Eigen only 20 seconds. Also, templated matrix and vector sizes are nice, because they resolve matrix size mismatches at compile-time. Finally, zero-based component indexing is what I expect, and the reason I don't suggest converting old projects from NewMat. Also be aware that the Eigen quaternion library uses the convention , which is fine and internally consistent, but not consistent with the math I had seen for quaternion derivatives. As a consequence, my code is liberally festooned with q.conjugate() and in some places q.conjugate().conjugate(). It's almost a case of two wrongs making a right, but not quite.
-
2013-Oct-10 - Calibration
04/05/2017 at 13:11 • 0 commentshttps://stkwans.blogspot.com/2013/10/calibration.htmlIt's kinda weird, but it turns out that C++ is the best language for processing Rocketometer data. There is a cool library NewMat which creates appropriate operator overloads to do matrices in C++, and I have extended it to include quaternions. C++ was doing in minutes what it was taking IDL hours. However, it took me 5 days to translate from IDL to C++, so I had better process a lot of data to ever get that time back.
The time that the Rocketometer spends in zero gravity may be the most valuable calibration time I ever get. Zero acceleration, zero rotation, and I expect a wide range of temperatures.
Remember that the goal of this is to get something into space, but that goal requires no further effort on my part to achieve. The secondary goal is to be able to calibrate the data and report something useful to Tom. The long-term goal though is to measure the track of Space Mountain.
So I am putting something in Space to get it ready for a roller coaster.
That made me laugh.
-
2013-Jul-31 - Rocketometer has been integrated
04/05/2017 at 12:56 • 0 commentshttps://stkwans.blogspot.com/2013/07/rocketometer-has-been-accepted.html
The rocketometer is now an integral part of the rocket payload. It is mechanically attached to the CCD heater card, a part of the payload which is the real purpose for this flight. It has power and ground connections to charge the battery, but power is only available for the first 9 minutes of the 15-minute flight, which doesn't even include reentry, the most dynamically interesting part of the flight -- so, the LiPo battery cell is there charging from the power input, but powering the board itself when the external power goes out. That is what makes the Rocketometer an independent spacecraft, even though it is physically bolted to another spacecraft.
Attached to the CCD heater card In the control section card cage And here we have a short video demonstrating the size of the Rocketometer: