Changelog¶
The canonical changelog lives at the repository root
(CHANGELOG.md)
so it stays next to the code it describes. This page embeds it directly so
the same content is browsable as part of the docs site, without a second
copy to keep in sync.
Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]¶
[1.0.2] - 2026-10-13¶
Documentation release. No library code changed; the figaroh-examples
v1.0.0 tag remains the validated pair.
Added¶
- Documentation site redesigned around user journeys (#201): a landing page with a task picker, Start / Workflows / AI agents / Concepts / Reference / Project navigation, five-step calibration and identification guides (Plan, Design, Configure, Fit, Verify) with an example/own-robot toggle and choice cards, Quickstart and "Add your robot" pages.
- AI agents section: agent setup, environment doctor, prompt library and
skills reference; the site publishes
llms.txtandllms-full.txt(mkdocs-llmstxt, added to thedocsextra) and every page has a Copy as Markdown button. - README screenshot of the documentation landing page.
Changed¶
- pre-commit
check-yamlruns with--unsafesomkdocs.ymlcan use mkdocs-material!!python/nametags.
[1.0.1] - 2026-10-12¶
Documentation and housekeeping release. No library code changed; the
figaroh-examples v1.0.0 tag remains the validated pair.
Added¶
- FIGAROH logo: the mark and a README banner in
docs/source/assets/, used by the README, the docs home page and the docs site header and favicon.docs/development/make_logo.pyregenerates them (#198).
Changed¶
- Copyright notices updated to 2026 for the maintainer and corrected to
2022–2024 for CNRS/Toward SAS;
CONTRIBUTING.mdrelease step 2 now checks that the notices include the release year (#197). ROADMAP.mdrecords the 1.0.0 release (#196).
[1.0.0] - 2026-10-12¶
First stable release. Roadmap milestone M1 (trustworthy
physical-model workflows) is accepted: both reference workflows, calibration
and dynamic identification, are recorded on core 779e830 + examples
b167298 (#181). This release declares the public API and its stability
policy, removes the features deprecated before 1.0, and makes calibration
refuse to export a failed fit.
Scope notes for 1.0:
- The real-data references (TIAGo calibration and identification, UR10 calibration) are verified at execution scope: held-out error is reported, not gated. Gated prediction exists only on simulated or synthetic truth (UR10 dynamic truth, TIAGo calibration truth fixture).
- The TIAGo effort scale reads 13.5 % low, outside its ±10 % diagnostic tolerance; the joint torque constants are tracked in figaroh-examples#126.
- The TIAGo URDF export is lumped; the PAL calibration export was not run on hardware; TALOS contact calibration is a regression consumer only.
Upgrading from 0.7: convert any legacy flat config once with
python -m figaroh.utils.config_migration, drop a non-zero
regularization_coefficient, and use Python 3.10 or newer (see Removed and
Changed below).
Added¶
- Public API and stability policy for 1.0 (#184): a new "Public API and
stability" docs page (
docs/source/api/stability.md) lists the supported entry points per subpackage, the experimental areas (MuJoCo backend andfrom_mjcf,integration,visualisation,measurements, log-Cholesky LC1), the internal modules, the deprecated features and the deprecation policy: a public name warns withDeprecationWarningfor at least one minor release, is removed only in a major release after 1.0, and has a migration note here. - The
__all__lists now match that page, and a unit test keeps them in sync.import figarohalso importsdata,backendsandintegration(about 0.1 s more, mostly the optionalmujocoimport when it is installed).calibration.__all__addscalibration_tools,data_loaderandparameter;identification.__all__addsidentification_tools,parameterandphysical_fit;tools.__all__addsrun_archive,stagesandsolver;utils.__all__addsconfig_parseranderror_handling;optimal.__all__addsBaseTrajectoryIPOPTProblem;measurements.__all__is["Measurement"]. The public tools, config and error-handling modules gained their own__all__. Nothing was renamed or removed; names outside an__all__stay importable but are not covered.
Deprecated¶
Each warns with DeprecationWarning from 1.0 and will be removed in 2.0
(#185):
figaroh.tools.qrdecomposition.QR_pivotinganddouble_QR: useQRDecomposer.decompose_with_pivoting/double_decomposition.figaroh.tools.robotcollisions.CollisionWrapper: useCollisionManager.- The module-level wrappers in
figaroh.tools.robotvisualization(place,display_COM,display_axes,rotation_matrix_from_vectors,display_force,display_bounding_boxes,display_joints): use theRobotVisualizermethods. plot_calibration_results,plot_identification_resultsandsave_resultsinfigaroh.utils.results_manager: useResultsManager.- The flat
physical_consistency_enabledidentification config key: usephysical_consistency.enabled.
Changed¶
requires-pythonis now>=3.10, following Pinocchio (conda-forge builds 3.8 and 4.1 for Python 3.10-3.14;pin4.1 ships wheels for cp310-cp314). The Python 3.8 and 3.9 classifiers are dropped and 3.13 and 3.14 are added. CI adds Python 3.10 (lowest) and 3.14 (highest) test jobs next to the Python 3.12 jobs. Migration: Python 3.8 and 3.9 users stay on 0.7.x.- The package classifier is
Development Status :: 5 - Production/Stable. RobotIdentificationSystem.identify_parametersalso reads adata_dirstored undertasks.identificationof a unified config, since the flatidentification:section it was read from is gone.
Removed¶
Features deprecated before 1.0 are removed (#185, paired examples migration figaroh-examples#125). Migration notes:
- The legacy flat config format (deprecated since 0.4.6). Loading one through
load_param,figaroh.utils.config_parser.get_param_from_yamlor the optimal-trajectory loader now raises aConfigurationErrorthat namespython -m figaroh.utils.config_migration --input <legacy.yaml> --output <unified.yaml>; run that once per file. The migration tool still reads legacy files. Also removed with the format:figaroh.calibration.config.get_param_from_yaml,figaroh.identification.config.get_param_from_yaml, bothget_param_from_yaml_legacy, and theget_param_from_yamlre-exports ofcalibration_toolsandidentification_tools. Usefigaroh.utils.config_parser.get_param_from_yamlwith a unified config. get_param_from_yaml_with_warninginfigaroh.calibration.configandfigaroh.identification.config(since 0.2.0): callfigaroh.utils.config_parser.get_param_from_yaml.figaroh.utils.config_parser.get_calibration_param_from_yamlandget_identification_param_from_yaml(since 0.2.0): callget_param_from_yaml(robot, data, "calibration")orget_param_from_yaml(robot, data, "identification").- A non-zero
regularization_coefficient/coeff_regularize(since 0.6.0) is aConfigurationError. Remove the key (zero or absent is still accepted) and useestimation.method: map, which sets priors of physical size per parameter group.config_migrationwrites 0 and warns when it meets a non-zero legacycoeff_regularize.
Fixed¶
- A calibration whose solver failed can no longer be exported silently
(#182, M1 G-1).
BaseCalibration.STATUSis"FAILED"instead of"CALIBRATED"when the solver did not converge, andjoint_corrections()(the URDF export gateway),build_geometric_calibrationandexport_geometric_calibration_yamlraiseCalibrationErrorwhen thefitstage isfailed, recording stageexportas failed, asBaseIdentification.export_urdfalready does.allow_failed_fit=Trueexports for diagnosis: it warns, recordsexportasfallbackand notes the override in the run provenance. A solver failure on the first fit now returns a failed result instead of raising anUnboundLocalError. Fits that converged export exactly as before.
[0.7.0] - 2026-10-11¶
This release ships the dynamic identification reference workflow (roadmap
M1.2–M1.4, dynamic side; delivery packages D4 and D7): select an
identification stage, check its physical and solver verdict, verify it on
held-out data with per-joint units, export a URDF that reloads with RNEA
parity, and archive the run. The D4 comparison selected the direct LMI effort
fit (physical_fit) as the supported physical baseline; the nonlinear
log-Cholesky fit is not included (D5 no-go, LC1 #155). Release tracker: #177.
Behaviour changes when upgrading from 0.6.0, detailed in the entries below:
- Base parameters are selected deterministically (#116).
phi_baseis reproducible across platforms and Pinocchio versions and differs from 0.6.0 wherever the old pivoting was a tie;QRDecomposer(deterministic=False)restores LAPACK pivoting. - The physical-consistency projection projects the reconstruction, not the
nominal model (#163). Without
reconstructionenabled it isskippedand thephysicalstage isnot_run; it was recordedok. identification.select_stageandproblem.torque_fit_jointsare new and opt-in; unset, results are as before. A rejected requested stage is a hard reject (#61).- Package metadata declares the license as Apache-2.0; it declared BSD.
Known limitations: projected is not a selectable stage;
merged_bodies="subtract_fixed" refuses on real fits, so the TIAGo reference
exports a lumped URDF; TIAGo held-out prediction is reported, not gated (no
ground truth, a temporal held-out block of one recording), and arm_1's torque
constant is not identifiable from the 2021-07 recordings; export parity shows
file consistency, not hardware accuracy.
Added¶
identification.select_stage(fit,reconstruction,physical_fit) chooses the reported, validated, archived and exported estimate. A rejected requested stage is a hard reject (selected_stagenone, verify fails, export refuses). The default is unchanged. NewBaseIdentification.export_urdf, andmerged_bodies="subtract_fixed"in the URDF exporter. The direct LMI effort fit is public asfigaroh.identification.physical_fit(_physical_comparatorstays as an alias);ReconstructionResult.effective_methodis new. (#61)problem.torque_fit_jointsnames the active joints whose measured effort is fitted and scored. The other active joints keep their kinematics in the regressor; their effort is left out of the fit, the WLS weights, the per-joint statistics and the validation metrics, which list them undernot_fitted_joints. Unset, every active joint is fitted as before. (figaroh-examples#68)- Record the log-Cholesky feasibility spike with frozen synthetic trajectories, independent physical/Jacobian checks and machine-readable Pinocchio 3.7/4.1 results. The decision is revise: convergence/scaling must improve before a production solver is approved. (#22)
- Diagnose why the log-Cholesky spike's fits stop at their evaluation budget and freeze a revised protocol (budget 2000, convergence by termination status, three fresh seed sets) before confirmation. The fresh-seed confirmation is no-go: 8 of 24 gated fits still hit the budget. (#30)
Changed¶
- Rewrite
README.mdfor 0.7: a quickstart that runs (the UR10 identification reference in figaroh-examples), the identification stages, tested platforms, and links to the documentation site in place of the module tables and an invalid YAML example. The release sequence inCONTRIBUTING.mdnow includes revising both READMEs.
Fixed¶
-
Package metadata declares the license as
Apache-2.0(an SPDXLicense-Expression, withLICENSEas the license file), matchingLICENSE. It declared a BSD classifier and copied the whole license text into theLicensefield, so PyPI listed the wrong license. -
The physical-consistency projection projects the identified fit, not the nominal model. It now reads the full-parameter reconstruction, which runs first, and projects only the joints the reconstruction covers. Without
reconstructionenabled the block isskippedand thephysicalstage isnot_run; it was previously recordedokfor a projection of the CAD prior. (#163) - A configured
max_secondsin the SDP reconstruction and the LMI projection is passed to picos astimelimit; picos rejected the old option name and the solve was reported as failed. (#164) - Base-parameter selection no longer depends on BLAS rounding or on the order
of regressor columns.
QRDecomposerpicks base columns with the newdeterministic_column_pivots: largest residual first, columns within a relative1e-6of the largest are tied and the smallest parameter name wins.phi_baseis now reproducible across macOS/Linux and Pinocchio versions (the base parameters it reports differ from before wherever the old selection was a tie).QRDecomposer(deterministic=False)restores LAPACK pivoting; geometric calibration keeps it until #113. (#116) - Optimal-trajectory plots work for robots whose position and velocity
dimensions differ (
nq != nv, e.g. TIAGo) (#149). Positions were indexed with velocity columns, so the plot failed (index 24 is out of bounds) and was skipped with a logged error. Both plot paths now draw only the active joints, positions through each joint'sidx_qand velocities and accelerations through itsidx_v, labelled with the joint names.ResultsManager.plot_optimal_trajectory_resultsgainsq_indicesandv_indices; without them it plots every column and refuses (logged error) whennq != nv. The bottom row no longer gets a second, empty set of axes stacked on it for the time labels.
[0.6.0] - 2026-10-07¶
This release ships the calibration reference workflow (roadmap M1.1–M1.4, calibration side; delivery packages D2, C1–C4, W3 and S1) and the inertial URDF export (D6), with the core fixes found by the UR10 truth fixture (D3:
142, #143). The dynamic identification reference (D4, D7) is not in this¶
release; it is planned for 0.7.0. Release tracker: #146.
Behaviour changes when upgrading from 0.5.0, detailed in the entries below:
solve(outlier_threshold=...)is a position error in metres (it was a number of standard deviations) and outliers are now excluded (#98).regularization_coefficientis deprecated and defaults to 0; useestimation.method: map(#120). It is planned for removal in 1.0.- Exciting-trajectory optimisation adds collision constraints for robots
without an SRDF and checks clearance along the whole spline (#143); such
robots may need a collision-free joint range (UR10 example:
joint_box). - Results change on rerun: base parameters at full precision (#142), a
seeded identifiable-parameter selection (#99), and corrected
full_paramsandjoint_offsetsemantics (#101, #110). export_urdfrefuses what it cannot write instead of skipping it (#62) and writes complete inertial sets (#60).
Known limitations: calibration accuracy is demonstrated on TIAGo mocap only (held-out marker RMSE 3.8–4.2 mm); nothing is applied to a running robot, and export parity shows file consistency, not hardware accuracy; inertial export is verified with known parameters, not yet with identified ones.
Added¶
- Calibration with several points of one rigid body per sample (#119).
Each
measurements.markersentry is a point on the same tool frame with its own offset (pEEx_k, ...); rows are marker-major, as the CSV loader already ordered them.calc_updated_fkmno longer raises forNbMarkers > 1; the closed-form frame guess fits the base frame and all point offsets; the structural selection uses all six pose components when there are several points; metrics, validation, outlier distances and plots count every point of every sample; the terminal and HTML validation tables list the position RMSE of each point. The loader now refuses non-finite marker measurements (e.g. an occluded point), naming the CSV rows. Several points identify the same joint parameters as one and roughly halve standard errors; they did not improve held-out prediction on the TIAGo mocap reference, so examples adopt them case by case (docs/source/concepts/config_parameters.md, "Several points on the tool"). -
Inertial URDF export (#60):
export_urdfwrites identified standard inertial parameters (m_,mx_…mz_,Ixx_…Izz_, PinocchiotoDynamicParameters()convention) as URDF mass, centre of mass and centre-of-mass tensor (parallel-axis theorem), keeping an existing inertialrpy. Targets may be links or moving joints (as identification names them). Partial sets, joints whose Pinocchio body merges fixed-attached massive links,m <= 0and, unlessallow_infeasible=True, physically inconsistent sets are refused; each link's physical verdict is logged. Previously these names raised (#62). -
Identification validation per joint (#103):
validation_metricsgainsper_joint(RMSE identified/nominal in the joint's unit, N for prismatic, N·m for revolute; measured std; normalised RMSE; R²;predictive),unpredictable_joints(held-out RMSE not below the measured std: no better than a constant) andcorrelation_normalised(correlation of per-joint standardised signals). The verdict'svalidation_correlationnow uses the normalised value, andvalidation_unpredictable_jointscounts flagged joints; the pooledcorrelationis kept. Terminal and HTML reports show the per-joint table and flag unpredictable joints. -
Stage-aware verdicts and reports (#63).
verify()separates per-stage verdicts:verdict.stagesgainsdata,fit,validation(fallbackon training data, never held-out evidence),physicalandexportbeside the existing scoped entries.- New verdict fields:
stage_records,selected_stage,splits(the files and sessions that trained and validated the run). - Provenance records the figaroh checkout's own commit
(
software.figaroh_revision) beside the working directory's (git_commit), so a run names its core/examples pair. - The terminal and HTML reports show the stages and data; the run archive index carries the stage verdicts.
-
Fixed: the exported verification JSON's
stages(the scoped status map) was overwritten by the stage records; they are now understage_records. -
figaroh.data: data contract types from the accepted decision recorddocs/decisions/data-result-contract.md(#55, part 1 of 2; additive). TrajectoryData(identification): time with arecorded/assumedclock, joint names, per-signal origin, effort in its recorded units with a per-joint kind and unit (joint_torqueN·m,joint_forceN,motor_current, ...), the conversion applied, the recorded signal, and a valid-sample mask.check_effort(model, drive_gain_joints)refuses effort that is not joint effort unless a drive gain is identified.PoseObservations(calibration): postures with named points or poses, their frame, measurability, mask and sessions; constraint-only datasets (contact) declare a constraint instead of zero "measurements".from_csv/to_legacyreproduceload_dataexactly, with deleted rows masked instead, and leavecalib_configunchanged.DataSourceandSession(files with sha256, adapter, recording identity) andProtocol(versioned roles per session, checked against the files' sha256).- Data contract:
TrajectoryData.sample_index/PoseObservations.sample_indexrecord each sample's source row (default0..n-1;from_csvsets CSV rows, so masked postures are named by row), andPoseObservations.select_points(names)restricts observations to some tracked points, e.g. one of TIAGo's four mocap points (#131). - The base classes use the data contract (#55, part 2).
BaseIdentification:load_trajectory_data()may return aTrajectoryData. Its effort is checked against the model (drive-side kinds are refused: no drive-gain parameter exists yet), and its mask leaves samples out of the regressor rows after filtering and differentiation (with decimation, decimated samplekis samplek * factor). A subclass returning one must convert the effort withTrajectoryData.converted(), not overrideprocess_torque_data(). Legacy dicts work as before.BaseCalibration.load_data_set()reads the CSV throughPoseObservations(same arrays asload_data);del_listrows are masked, andcalibrator.observationskeeps every posture.figaroh.tools.stages: each run recordsdata,fit,validation(fallbackwhen evaluated on training data),physicalandexport(failedwhenjoint_corrections()refuses parameters) stages onobj.stages, in the results dict under"stages", in the run archive'sstages.json, and in the verification JSON, which also gainsschema_version.-
Identification results gain
"effort rmse"(N·m or N); the mislabelled"rmse norm (N/m)"is kept for existing readers. -
BaseCalibration.metrology_frames(): the fitted base frame and tool point, which a URDF does not carry; applied outside an exported URDF they reproduce the calibrated forward kinematics. Tested on a known TIAGo correction fixture for both levels and three methods, on fitted and unused postures (#62). -
BaseCalibration.joint_corrections(lift=True): the joint corrections to write into a URDF, the same values the PAL export uses (#111). -
Calibration estimation methods, chosen per robot and dataset with
parameters.estimation.method(#113); the defaultstructuralis unchanged. excitation: select over every joint parameter on the measured postures and drop those whose predicted standard error exceedsexcitation_ktimes their expected size.map: estimate every joint parameter with Gaussian priors of the expected sizes (priors); posterior standard deviations instd_dev.map_cv/cv_subset: prior scale or parameter-set size chosen by k-fold cross-validation over the training postures; no robot-specific sizes needed.- Diagnostics in
calib_config["estimation_report"]; guide indocs/source/concepts/calibration_estimation.md.
Changed¶
regularization_coefficient(calib_config["coeff_regularize"]) is deprecated and defaults to 0 instead of 0.01 (#120). Robot cost functions appendedsqrt(c) * thetarows for every joint parameter, so a metre and a radian were penalised alike. Core never applies the coefficient; a non-zero value now raises aDeprecationWarningpointing toestimation.method: map, which places priors of physical size on each parameter group. Design record:docs/decisions/calibration-regularisation.md. The figaroh-examples robots drop their regularisation rows in the paired PR.- PAL
geometric_calibrationexport (build_geometric_calibration,export_geometric_calibration_yaml) fixed and givennominal_urdf(#123). - Joint offsets exported:
joint_offset-level corrections (offsetRZ_*etc.) were dropped, so the TIAGo reference wrote an emptygeometric_calibration. - Deltas against the URDF origin: they were taken against Pinocchio's
joint placement, which merges preceding fixed joints (wrong frame behind
a rotated fixed joint, e.g. UR10
shoulder_pan_joint). - Exact rpy: the rpy delta is solved so that the origin's rpy as
written plus the delta is the corrected rotation, including at pitch
+-pi/2 (TIAGo arm_4..arm_6). Pass
nominal_urdf; without it the origin is recovered from the model. Where the joint axis is the axis roll and yaw share at pitch +-pi/2, no small rpy delta exists and the exact one trades roll against yaw (e.g.droll-pi/2,dyaw+pi/2): the same small rotation. -
Behaviour change: PAL files gain the joint-offset keys; values behind rotated fixed joints change.
-
export_urdfrefuses what it cannot write instead of skipping it (#62). A correction for a joint or link missing from the URDF, inertia tensors, first moments, legacyoff_*, and a joint with bothoffset*andd_*now raiseValueError; no file is written.joint_corrections()raisesCalibrationErrorfor fitted parameters a URDF cannot carry (elastic, contact planes); passdrop_unsupported=Trueto export the kinematic corrections only. Values are written with 12 significant digits instead of 6. -
Behaviour change: calls that relied on the logged skip now fail. Exported URDF text changes in the trailing digits.
-
redistribute_parameters()(and so the PALgeometric_calibrationexport and the HTML report) liftsstructuralbase parameters by a weighted minimum-norm lift instead of the Moore-Penrose one (#111). - Why: the old lift weighed metres and radians equally, ignored the rows the fit held at zero (base-frame and dropped rows), so it did not reproduce the fit, and the PAL export removed "the first six rows" by position instead.
- Now: expected sizes from
parameters.estimation.priorsweight the lift; base-frame and dropped rows are held at 0; Gauss-Newton steps make the lifted model reproduce the fit at the measured postures (skipped, first order only, when the calibrator has non-joint parameters such as contact planes). The result does not depend on which representative the QR chose. The PAL export no longer excludes the first joint's parameters. -
Behaviour change: PAL and report values for
structuralchange. -
Calibration drops joint parameters that the estimated base/tip frames absorb, starts the frames at a closed-form estimate, and selects parameters deterministically (#102, #99).
- Absorbed parameters dropped: after the structural selection,
BaseCalibration.create_param_listbuilds the full measurement Jacobian at the measured configurations (configured measurability, base and tip columns included) and drops every joint parameter that is a combination of the frames and the joint parameters before it (eliminate_absorbed_parameters, tolerance 1e-4 on unit-normalised columns). The names are kept incalib_config["absorbed_param_name"]. TIAGo mocap:joint_offsetdrops the torso and arm_1 (rank 14/14 instead of 14/16);full_paramsdrops the tool-absorbedd_pz_arm_7_joint(with #110; before it, 4–9 parameters including an RPY near-singularity). - Closed-form start:
solve_optimisationstarts unknown base/tip frames atestimate_frames_closed_form(Kabsch / chordal mean alternated with linear least squares) instead of zero. - Deterministic draw: the structural random configurations come from a
seeded NumPy generator (
calib_config["random_seed"], default 0) instead of Pinocchio's global RNG. - Behaviour change: the calibrated parameter set is smaller when frames
are estimated. Set
calib_config["eliminate_absorbed_parameters"] = Falsefor the previous set. full_paramsplacement errorsd_{px,py,pz,phix,phiy,phiz}_<joint>now act in the joint frame,placement · SE3(exp3(d_phi), d_p), the convention of the kinematic regressor that selects the base parameters (#110).- Cause:
update_joint_placementaddedd_p*to the parent-frame translation andd_phi*to the placement's RPY angles. Wherever the nominal placement is rotated, the fitted model then had different dependencies from the ones selection assumed: FK derivatives differed from the regressor columns by O(1) on TIAGo arm_1–arm_7 and UR10 shoulder_pan, shoulder_lift, wrist_2 and wrist_3. - Effect: TIAGo mocap
full_params(figaroh-examples held-out protocol, macOS): 31 instead of 28 parameters, norm RMSE 1.64 / 3.07 / 2.83 / 2.44 mm (training / validation / two confirmation sets) instead of 1.75 / 3.68 / 3.35 / 2.89 mm. - Export:
export_urdfapplies a joint's sixd_*values together asorigin · SE3(exp3(d_phi), d_p);build_geometric_calibrationconverts them to PAL origin xyz/RPY deltas against the nominal placement. - Behaviour change:
full_paramsvalues from earlier versions use the old convention and are not comparable on rotated placements (UR10, Talos, TIAGo).joint_offsetis unaffected.
Fixed¶
- The exciting-trajectory optimiser enforces collision clearance along the
trajectory (#143). Its collision constraint was empty when the robot's
geometry model had no collision pairs (no SRDF, as for the UR10): every
two geometries on non-adjacent bodies are now paired (
srdfremoves pairs). Collision was checked at waypoints only, so splines crossed obstacles between them; initial guesses and every solved segment must now keepcollision_marginalong the whole spline (collision_check_frequency, default 200 Hz), andcollision_checks_per_intervaladds check points inside the constraint.collision_screencomputes exact distances only for nearby pairs. Robots whose trajectories crossed obstacles now need a collision-free joint range (UR10 example:joint_box). - Identified base parameters are returned at full precision (#142).
QRDecomposerroundedphi_bto six decimals on every path, and the base values computed from priors to five; both weighted-least-squares refinements rounded to six. Small base parameters (UR10 wrist terms are ~1e-5) lost up to 5 %, and noise-free effort was fitted to ~5e-6 N·m instead of rounding error. Rounding remains only in displayed text. -
Calibration's identifiable parameter set no longer depends on global random state (#99). The structural selection draws its random configurations from a generator seeded by the new
parameters.random_seed(default 0, legacyrandom_seed), and the seed is recorded in the run provenance. Repeated runs in one process give the same parameter set and result, whatever ran before. The seeded draw itself landed with #102. -
Calibration outlier removal now removes outliers (#98). The loop in
BaseCalibration.solve()used to flag samples by a 3-sigma rule and refit the same full dataset, while the configuredparameters.outlier_thresholdwas unused. Samples whose position error exceedsoutlier_threshold(metres,outlier_eps; rotational components do not take part) are now excluded from the fit and the rest refitted, for up tomax_iterationsfits.solve(outlier_threshold=...)now overrides the config in metres (defaultNone: the config; it was a number of standard deviations). RMSE, MAE, max error, per-DOF statistics and the residual degrees of freedom cover the kept samples; the evaluation addsexcluded_samples,excluded_sample_errorsandoutlier_threshold, and the terminal and HTML reports list them. Thefitstage metricflagged_outliersis nowexcluded_outliers. -
Calibration quality reports label residual rows from
measurability(#100). A contact calibration measuring[z, roll, pitch]was printed asX/Y/Z (mm)with the two rotations folded into "Position RMSE". Rows are now named and scaled per measured component (mm for positions, deg for rotations), position/orientation aggregates cover only the measured components of each kind (NaN and "not measured" when none), andverify()picksposition_rmse_mm/orientation_rmse_degfrom the measured kinds rather than the component count. With no residual degrees of freedom,calc_stddev()reports the uncertainty as not estimable (NaN, a warning,residual_dofin the evaluation metrics) instead of dividing by zero. Reports no longer claim there is no validation data when a subclass evaluates its held-out set itself. -
calc_updated_fkmwith an empty parameter list (nominal FK) raisedUnboundLocalError; it now returns the nominal model's measured frame (#129). -
URDF export wrote corrections into
<transmission>joints instead of the robot joint when a transmission came first in the file (#114). - Cause:
_find_jointsearched every<joint>in the document; ros_control<transmission>blocks contain<joint name=...>too. It now searches only the<robot>element's direct children (same for links). -
Effect: figaroh-examples UR10 exports were identical to the nominal URDF (its transmissions precede the joints); TIAGo and Talos were not affected.
-
Calibration parameter standard errors (
std_dev,std_pctg, the covariance behindredistribute_parameters) were ~100× too small (#107). - Cause:
calc_stddevestimated the residual variance asresult.cost**2 / (m - n), butleast_squares'costis already0.5 * sum(fun**2). - Fix: it now uses
sum(r**2) / (m - n)over the measurement residuals, excluding regularisation rows a subclasscost_functionappends. The covariance still uses the full Jacobian. - TIAGo mocap: arm_5's offset SE goes from 0.088 to 10.1 mrad. The
PAL "conservative (≥ 2σ)" YAML keeps 19 of 41
full_paramscorrections instead of all 41. add_base_nameshiftsbase_mapping_slicewhen it prepends the base names atjoint_offsetlevel; the slice pointed six entries too early.- Calibration held-out data (
validation_data_file) works (#105). - Config key read:
tasks.calibration.data.validation_data_fileis now read from the unified config. Before, only a CLI override reachedcalib_config. - Training count preserved: loading the validation CSV no longer
overwrites the training
calib_config["NbSample"]. A larger validation set crashedsolve(), and a smaller one silently truncated training. - Unpack order fixed:
_load_validation_datano longer swaps poses and joint configurations. - All samples used: validation metrics now evaluate every validation
sample (
_compute_logmap_residualstakesn_samples). - Failures logged: a validation file that fails to load now logs a warning instead of being skipped silently.
- Calibration
joint_offsetparameters (offset{PX,PY,PZ,RX,RY,RZ}_<joint>) are now offsets of the joint configuration,q + offset(#101). They were added to the joint placement's parent-frame RPY, a rotation about the parent axis, which differs from a joint offset whenever the joint axis is not the parent's: up to 47 mm at the TIAGo tool for 0.05 rad on arm_2. Newcalibration_tools.apply_joint_offsetcomposes the offset in the joint frame; elastic deflections (k_*,non_geom) use it too, and placements are restored exactly after each FK evaluation.full_params(d_p*,d_phi*) is unchanged. Behaviour change:joint_offsetresults from ≤ 0.5.0 on joints whose axis is not the parent's z are not joint offsets; refit them. On the corrected TIAGo 2021-11-30 mocap data the fit now returns arm_5 −49.7 mrad and arm_2/arm_6 near zero, instead of +22.8 / −31.7 mrad. export_urdfwrites joint offsets into the joint<origin>(about the joint's own axis), so the reloaded URDF reproducesq + offset. They were written to<calibration rising>, which Pinocchio and robot_state_publisher ignore, so exported joint offsets had no effect on reload (#101).
[0.5.0] - 2026-10-03¶
Changed¶
-
The blocking
Lintjob runs every pre-commit hook on the full tree instead of only on changed files, and the advisoryLint backlogjob is removed: the legacy lint debt inventoried in #57 was cleared in #81-#85. -
Replace universal quality gates with scoped execution/prediction verification; preserve diagnostic metrics, record incomplete required evidence, reject nonfinite results, and require explicit per-output limits for prediction acceptance. (#70) Behaviour change:
verify()withoutscopenow means prediction acceptance, so callers that only checked.passedgetFalse(not evaluated) unless they supply separately loaded validation data and limits; passscope="execution"for numerical-execution checks. The default thresholds are now empty, and training-data fallback metrics are namedtraining_*. -
Bound Pinocchio to
pin>=3.7,<5, replace deprecated frame-parent access, and add explicit Pinocchio 3.7.0 / 4.1.0 CI profiles with compatible ndcurves versions. Constraints apply before environment creation; CI checks native imports, installed versions and dependency consistency. -
Require one focused PR per completed issue and explicit maintainer approval before agents merge, with the policy recorded in CONTRIBUTING, AGENTS and the PR review checklist.
-
Reorganize the roadmap around delivery outcomes and exit gates, preserve its previous detailed planning snapshot in an archive, and replace the architecture inventory with a source-audited description of current contracts and limits.
- Document the
develintegration /mainrelease workflow in CONTRIBUTING, refresh agent guidance, and add issue/PR templates and a design-decision index. - Add Python 3.12 core and MuJoCo 3.9/3.14 test jobs, critical whole-tree lint,
changed-file pre-commit checks, and an advisory report of existing lint debt.
Documentation builds now check PRs to both development and release branches;
installation failures propagate, and deployment only runs on pushes to
main. CI fetches the pinned examples mesh fixtures required by the full suite and fixes a platform-sensitive QR precision assertion to use absolute tolerance.
Fixed¶
- Optimal trajectory generation no longer exhausts memory or the time budget
(figaroh-examples#50). Pivoted QR in
qrdecompositionuses economic mode instead of building a full m×m Q (13.7 GB for UR10 at setup; TIAGo was killed). The trajectory problem uses forward-difference objective gradients instead of numdifftools,calc_torquecalls RNEA once per sample instead of once per joint, and initial waypoint steps are clamped to the velocity limits so the feasibility search succeeds. Per-sample constraint-check messages are DEBUG, not WARNING. RobotIPOPTSolverno longer reports IPOPT status -1 (maximum iterations exceeded) as success. Behaviour change: a trajectory segment that stops at the iteration limit is kept only if its final iterate is feasible; it is then markedconverged: Falsewith a warning. The iteration cap is configurable asproblem.max_iterations(default 200).- Configuration variants that
extendsa section (e.g.tasks.calibration) now override that section. They were merged into the configuration root, so the selected task silently kept its base values (#76). figaroh.utils.config_parser.get_param_from_yaml()accepts whole legacy files withcalibration:/identification:sections and dispatches the requested one;task_type="auto"selects a single present section and still requires an explicit task when both exist (#76).-
Configuration differentiation now estimates acceleration for every tangent coordinate, including the last fixed-base joint. Derivative widths use
nvrather thannqor effort-selection flags, supporting quaternion/continuous joints through model/backend differences. Uneven intervals use velocity midpoint times; malformed/nonpositive/nonfinite intervals fail explicitly. The legacy trailing-two-sample trim and interval velocity alignment remain. Older UR10 dataset fits remain preprocessing-limited until torque-generation provenance and sampling/filter assumptions are audited separately. (#32) -
Physical-consistency fallback, SDP projection and SDP reconstruction now distinguish link-origin rotational inertia from the second-moment block of pseudo-inertia. Projection weights and returned p10 entries remain in dynamic-parameter coordinates; reconstruction enforces inertia triangle inequalities as well as positive mass. Previously SDP fits could accept physically invalid inertias. First-moment projection weights now penalize each component independently instead of only a weighted sum, preventing changes to feasible nonzero-CoM inputs. URDF reconstruction priors now use link-origin tensor entries via Pinocchio's dynamic-parameter conversion, including the parallel-axis contribution of a nonzero CoM. (#21)
-
MuJoCo mass-matrix evaluation now supports both
mj_fullMsignatures: the pre-3.10(model, destination, qM)form and the newer(model, data, destination)form. This fixes failures with recent MuJoCo versions that removeddata.qM. -
get_standard_parametersread each joint's CAD inertia from the body before it (model.inertias[i]instead of[i + 1]), sotau_ref, the nominal validation torque/improvement_pctand the reconstruction prior were all off by one body. Base parameters were unaffected. (#11) - Regressor extra columns (
fv,fs,Ia,off) are now laid out from the enabled flags instead of fixed offsets. Previously any combination other than "friction only" or "all three" raisedIndexError.add_standard_additional_parametersnow only emits enabled blocks, so parameter keys always line up with regressor columns. With friction only, the parameter dict no longer carries unusedIa_*/off_*keys. (#12) - Unified config:
signal_processing.sampling_frequency,cutoff_frequencyandfilter_ordernow default the filter'sf_sample/f_butter/nbutter, which were stuck at 100 Hz / 2 Hz / 4. Explicitfilter_paramsstill win. A cutoff at or above Nyquist raisesValueError. (#13)
Added¶
-
Trajectory optimisation can retry a failed segment from a new initial guess:
problem.segment_attempts(default 1, no retry). See figaroh-examples#60. -
tasks.identification.problem.qr_relative_tolerance: QR rank threshold as a fraction of the largest pivot. Default unset (unchanged behaviour). A warning suggests it when the base regressor's condition number exceeds 1e6. (#14)
[0.4.8] - 2026-09-07¶
Added¶
skills: Newskills/directory shipping six agent skills that take a user from a fresh clone to a running task, so an AI coding agent can set each task up without first re-deriving the workspace layout. Mirrored infigaroh-examples/skills/(same content, paths resolved against$FIGAROH_WS, the directory holding both repos side by side):figaroh-start— the router. Maps the workspace (which of the sibling repos owns what), carries a robot x task x config matrix, states the two path rules every example script depends on (run fromexamples/<robot>/;package_dirs="../../models"), and dispatches to the skill below.figaroh-setup-env— environment bootstrap, with an executablescripts/doctor.sh: read-only, exits 0/1, locates the workspace root on its own, and prints a specific remedy per failure. It checks the failure that is otherwise invisible — whetherimport figarohresolves to the localsrc/tree or to a shadowing site-packages copy.figaroh-setup-calibration,figaroh-setup-identification,figaroh-setup-optimal— one per task: required inputs, the exact config block, the real CLI flag surface, how to read the run archive, and a failure/cause/fix table. The data contracts are documented fromcalibration/data_loader.pyand each robot'sload_trajectory_data()rather than from prose, because both are derived rather than fixed: calibration CSV columns follow each marker'smeasurable_dof, and identification has no single schema at all.figaroh-setup-new-robot— onboarding a robot with no example folder, from description package tovalidate.py --robot <robot>exiting 0. The set is self-contained by design: it assumes onlyfigarohandfigaroh-examples, references no other package or checkout, and points at repo directories rather than at skills outside the set.docs: README now points atskills/for agent-assisted setup.
Fixed¶
tools:build_total_regressor_wrenchraisedNameErroron every call. Its signature named the settings dictparamwhile the body readidentif_configthree times -- an incomplete rename that made the exported function unusable. Renamed the parameter toidentif_config, matching both the body and the siblingbuild_total_regressor_current. Nothing called it and nothing tested it, which is how a fully broken public function went unnoticed;tests/unit/test_regressor_wrench.pynow covers it (7 tests, verified to fail if the rename is reverted).optimal:BaseOptimalCalibration.save_resultsreportedconfiguration_countaslen(self.optimal_configurations), which counts the dict's keys (always 2) rather than the selected configurations. A UR10 run wroteconfiguration_count: 2to the summary CSV while printing "Selected 73" to stdout. Addedcount_optimal_configurations()and used it, so the saved summary now matches what the run reports.optimal: the D-optimality determinant root was saved under the keycondition_number, which is a different quantity entirely and made the CSV actively misleading. Renamed tod_optimality_detroot. Breaking for anything reading that column out of a saved optimal-calibration CSV/YAML.optimal:tasks.optimal_configuration.output.output_filewas silently ignored —load_paramonly ever loads thecalibrationtask, so the optimal task's ownoutputblock never reached the object, andsave_resultshardcodedresults/. The configured directory is now honoured (the filename stays managed/timestamped), falling back toresults/when unset, so existing behaviour is unchanged for configs that do not set it.
Tests¶
tests/unit/test_optimal_results.py: 17 new tests covering the three fixes above — configuration counting, output-directory resolution, and reading the optimal task's output block (including the YAMLNone-as-string trap, legacy configs, and missing files).save_resultspreviously had no coverage.tests/unit/test_regressor_wrench.py: 7 new tests callingbuild_total_regressor_wrenchfor real — output shapes, finiteness,mass_loadscaling, andwhich_body_loadedblock selection. Verified to fail with the originalNameErrorif the fix is reverted.
Changed¶
build: Pinned the build backend tohatchling<1.32. 1.32.0 emitsMetadata-Version: 2.5, which the current release toolchain rejects --twine checkfails with "'2.5' is not a valid metadata version" becausepackaging25.0 recognises only up to 2.4. Every hatchling through 1.31 emits 2.4.requireswas previously unpinned, so each build silently took whatever was newest; this also makes release builds reproducible.docs: Updated README's Examples Repository table with recentfigaroh-examplesadditions: TIAGo Pro right-arm geometric calibration (contributed by Clement Pene), TALOS's single-plane table-contact whole-body calibration (no longer "to be released" — it shipped), and TIAGo's new experimental suspension-identification and empirical-backlash-surface examples.docs: Splitdocs/decisions/tiago-suspension-backlash-and-modular-terms-plan.md(an uncommitted working draft) into two focused documents reflecting their different implementation status:tiago-suspension-backlash-examples.md(done — the TIAGo suspension-identification and empirical-backlash-surface examples shipped infigaroh-examplesPR #9, with an implementation summary, concrete real-data results, and known gaps against the original plan) andmodular-linear-residual-terms-plan.md(still proposed — the genericLinearRegressorTerm/ResidualTerm/WeightPolicyarchitecture and the unbuilt physical/stateful backlash model). Also correctedROADMAP.md§8.2's stale "not started" status for the suspension port (superseded by the new document) and added both new documents toROADMAP.md's References table anddocs/source/further_reading/decisions.md's index.docs: Consolidateddocs/decisions/from 8 documents to 6 by merging related comparisons/analyses/reviews into single multi-part documents, each carrying its own verified implementation-status section rather than trusting the original "no implementation yet"/status-line claims:external-tool-comparisons.md— Part A (robot_calibrationcomparison- calibration-composability adaptation roadmap), Part B (MuJoCo
sysidcomparison), Part C (the full reporting/verification infrastructure build-out that Part B's comparison produced).
- calibration-composability adaptation roadmap), Part B (MuJoCo
tiago-calibration-and-port-review.md— Part A (TIAGo/TIAGo Pro structural & statistical calibration analysis, redistribution rationale), Part B (eye-hand + suspension identification port review).- Remaining decision docs renamed to consistent kebab-case filenames
(
figaroh-examples-improvement_plan.md,urdf_exporter.md,validation-quality-report.md), with stale status markers corrected against the current codebase (e.g.IMPROVEMENT_PLAN.md's Phase 7 was falsely marked all-complete; only 1 of 4 items actually was). docs: RewroteROADMAP.md(v2.0 → v2.1) as a single coherent document: added a table of contents, a References section linking every roadmap track to its sourcedocs/decisions/document, and a consolidated Estimated Timeline spanning all tracks. Added three tracks that previously existed only as detailed decision docs with no roadmap-level rollup — Track C (Reporting, Verification & Quality Infrastructure), Track D (Calibration Layer Composability,robot_calibration-inspired), Track E (Example Ecosystem Parity & Robot Ports). Re-verified every Track A/B status marker against the codebase and test suite rather than carrying the prior draft forward; corrected one stale claim (integration/api.py/RobotIdentificationSystemis implemented, was marked "not started"). Removed the old per-version "Revision History" section, which had drifted into duplicating this changelog, in favor of a short roadmap-document-only history.docs: Added Track F — Deployment & Sim-to-Real Integration toROADMAP.md, explicitly marked as ongoing research rather than a committed track. Sourced from a newdocs/decisions/sim2real-modelbased-deployment.mdpositioning document (moved in from a working draft), which proposes a model-based deployment/control layer for RL/IL policies as FIGAROH's headline value proposition alongside its existing identification/calibration role. Cross-referenced its phase-level overlaps with existing tracks (backend cross-validation, online identification, URDF/friction export) so they supersede rather than duplicate the corresponding Track A/B/D/E items, and flagged ecosystem-integration questions (e.g. mjlab, motion-retargeting libraries) as open research, not yet scoped into any phase.
[0.4.7] - 2026-08-07¶
Added¶
feat(calibration):BaseCalibration.redistribute_parameters()— minimum-norm redistribution of fitted base-parameter values (and their covariance) onto the full standard-parameter set, viafigaroh.tools.qrdecomposition.redistribute_min_norm/propagate_covariance_min_norm(Moore-Penrose pseudoinverse of the base-mapping matrixM,phi_base = M @ theta_r). Previously,calculate_base_kinematics_regressoralready computed this combination internally but discarded it, deploying each fitted value under one arbitrary representative parameter name with the rest of its redundant group implicitly left at 0 — this is one choice among infinitely many that reproduce the same fit. Redistribution spreads the fitted value across the whole group instead (round-trips exactly to the same predictions), and the propagated covariance flags which directions are genuinely underdetermined rather than reporting false precision. Opt-in; no change to existingsolve()/report behavior. Seedocs/decisions/tiago-calibration-and-port-review.mdPart A §A.8 for the full motivation and literature context.feat(report): the HTML calibration report now includes a "Redistributed standard parameters" section, calling the aboveredistribute_parameters()on the live calibrator and rendering it through the same_param_uncertainty_sectiontable used for the base-only fit — degrades to a muted "not available" message rather than failing when redistribution isn't possible for a given run.feat(tools):figaroh.tools.geometric_calibration_export—build_geometric_calibration()/export_geometric_calibration_yaml()produce the PAL Roboticsrobot_state_publisher.geometric_calibrationruntime-correction YAML (the format hand-curated to date infigaroh_tiagoPro/data/master_calibration_*.yaml) directly from a solvedBaseCalibrationinstance's redistributed parameters, with an optionalmin_sigmathreshold reproducing the "conservative" (≥2σ) deploy variant. Reusesurdf_exporter._parse_param_namefor joint/axis parsing; excludes the co-estimated-base-merged parameter block structurally (viabase_mapping_row_names) rather than by name pattern, so it's robust to whatever display renaming aBaseCalibrationsubclass applies tocalib_config["param_name"].feat(examples):tiago,tiago_pro,ur10, andtalosexample calibration scripts now exportmaster_calibration.yaml(full) andmaster_calibration_conservative.yaml(≥2σ) to the run directory by default alongsidereport.html, via the newgeometric_calibration_exportmodule — new--geometric-calibration-yamlCLI flag (default on) to opt out, mirroring the existing--html-reportflag.feat(report): both the "Parameter uncertainty" table (base-only fit) and the new "Redistributed standard parameters" table now show each parameter's actual identified value, not just its standard error —_param_uncertainty_sectiongained an optionalvaluescolumn, andBaseCalibration's stored evaluation metrics gainedparam_values(result.x) alongside the existingparam_stdev/param_stddev_percentageso the report has a value to show. The identification report's base-parameter table picks up the same column for free (phi_basewas already computed there, just not threaded through to the table). Backward compatible: a caller/older run withoutparam_valuesrenders "—" instead of failing.
[0.4.6] - 2026-08-06¶
Fixed¶
fix(calibration):calc_updated_fkmnow supports joint elasticity (non_geom) and camera/ref-frame base composition — capabilities previously implemented only in a second, dead-code function,update_forward_kinematics(zero callers infigaroh/figaroh-examples), which had regressed relative to its own predecessor and was deleted as part of this fix:- The base/camera transform (
bMo/wMo) was computed but never composed into the returned pose. - Elasticity indexing (
xyz_rpy[elas_id + 3]) went out of bounds for any rotary joint — i.e. every revolute joint. - The elasticity parameter-match loop reused a stale
keyleft over from an unrelated earlier loop instead of iterating its own. - The per-sample pose write was gated on a parameter-count bookkeeping variable that accumulated across the whole sample loop instead of resetting per sample, silently zeroing out later samples.
- (Found while implementing the merge) the elasticity branch also read
data.oMf/get_rel_transformwithout re-running forward kinematics after perturbingmodel.jointPlacementsfor the deflection, so the elastic offset never actually reached the output pose — fixed by re-running FK before computingoMee. calc_updated_fkmpreviously silently ignoredcalib_config["non_geom"]entirely (every real caller uses this function, not the dead one) — it now actually applies elasticity when the flag is set.NbMarkers > 1now raisesNotImplementedErrorinstead of silently logging a warning and falling back to an identity marker transform.- Verified against the TIAGo Pro reference calibration dataset
(
figaroh-examples/examples/tiago_pro, 94 samples): the existingfull_params/known_baseframe=Falsegeometric path is numerically unchanged (bit-identical output for a fixed parameter set and data). fix(config):unified_to_legacy_configreadparameters.free_flyer, a key the unified schema never defines (the template puts it atkinematics.free_flying_base) — a unified config settingfree_flying_base: truewas silently ignored, always resolving tofree_flyer=False. Extraction moved to_extract_frame_info, reading the correct key.fix(config): eye-hand calibration (base_to_ref_frame/ref_frame) had no unified-format equivalent, even though the schema template already reservedtasks.calibration.eye_hand.{camera_frame,reference_frame}for it —unified_to_legacy_configjust never read that section. Added_extract_eye_hand_params, wired in.fix(calibration):BaseCalibrationreported RMSE/MAE two different ways that could disagree by exactlysqrt(n_dofs)depending on which number you looked at —_evaluate_solution'sevaluation_metrics["rmse"]/["mae"]flattened every x/y/z/... residual component into one list (a "mean of squared components" convention), while_compute_per_dof_stats'spos_rmse_mm/orient_rmse_degand the validation table reduced each sample's residual vector to one Euclidean-norm distance first, then aggregated. For position-only calibration these differed by exactlysqrt(3), always. Converged_evaluate_solution,_detect_outliers, and_store_optimization_results's_PEE_distonto the per-sample-distance convention — the physically meaningful one (an actual 3D positioning error in mm) and the one most of the report already used. Outlier detection is unaffected (scale-invariant comparison). Also addedpos_mae_mm/orient_mae_degto_compute_per_dof_stats's "overall" block (RMSE already had a position/orientation split; MAE didn't), surfaced as "Position MAE"/"Orientation MAE" in both the terminal quality report and the HTML report (report.py). Re-verified against tiago, tiago_pro, ur10, and talos calibration runs.fix(calibration):calc_stddev()ran after_compute_parameter_correlation()inBaseCalibration, but correlation readsself._C_param, whichcalc_stddev()is what sets — on a fresh instance's firstsolve(), correlation silently returned[]instead of the actual pairs. Reordered socalc_stddev()runs first.
Added¶
feat(config): Legacy flat config format (calibration:/identification:top-level sections) is now deprecated in favor of the unified format — loading one emits aDeprecationWarning(+logger.warning) fromfigaroh.calibration.config.get_param_from_yaml/figaroh.identification.config.get_param_from_yaml, the single parser every legacy-format caller funnels through regardless of entry point.feat(utils):figaroh.utils.config_migration— converts a legacy config to unified format automatically (python -m figaroh.utils.config_migration --input legacy.yaml --output unified.yaml), including the eye-hand and free-flyer fields fixed above. An optional--urdfflag runs a round-trip self-check (converts, then re-parses through the realunified_to_legacy_*configand diffs against the original) rather than trusting the conversion blindly. Fields with no unified-format consumer today are preserved undercustom:instead of silently dropped. See "Migrating from legacy to unified format" indocs/source/concepts/config_parameters.md.
[0.4.5] - 2026-07-13¶
Added¶
feat(identification): Weighted least squares (WLS) support —BaseIdentification.solve(wls=True)refines the OLS base-parameter estimate with iteratively-weighted least squares (Gautier, 1997) before computing quality metrics, so the terminal/HTML report andverify()verdict reflect the WLS-refined parameters rather than a stale OLS estimate. Config-driven viaidentification.problem.wlsin the unified YAML config, following the existinghas_friction/has_joint_offsetpattern.feat(tooling):figaroh.tools.provenanceandfigaroh.tools.run_archive— every calibration/identification run now carries a provenance record (git commit, config hash, timestamps, robot/asset identity), andarchive_run()writes each run to a timestampedresults/runs/<robot>/<task>/<timestamp>/directory (config snapshot, provenance JSON, report) instead of overwriting a singleresults/path.feat(validation): When no separate validation dataset is configured, calibration and identification now fall back to evaluating validation metrics against the training data itself — rather than silently omitting the validation section — with an explicit warning surfaced in the terminal report, HTML report, and machine-readable insights.feat(reporting): Self-contained HTML diagnostic reports for both calibration and identification.figaroh.tools.report.generate_calibration_report()/BaseCalibration.export_html_report()— summary, auto-generated insights, per-DOF residual table, parameter uncertainty bars, correlation section, validation section.figaroh.tools.identification_report.generate_identification_report()/BaseIdentification.export_html_report()— same shape, adapted to identification's per-joint torque residuals and base-parameter uncertainty.figaroh.tools._report_common— shared HTML/CSS kernel (styling, escaping, insight/uncertainty/correlation section builders) reused by both generators.- Opt-in via
solve(html_report=True)on both base classes, matching the existingplotting/save_resultsflag pattern. feat(identification):BaseIdentification.print_quality_report()— terminal quality report (condition number, RMSE, per-joint residuals, base-parameter uncertainty, validation, physical-consistency/ reconstruction status), the identification analogue of calibration's existing terminal report.feat(identification): Held-out validation support for identification, mirroring calibration's — a genuinely separate dataset viaidentification.data.validation_data_file, never a runtime split of training data.feat(reporting): Machine-readable verification verdicts.VerificationVerdict/ThresholdCheck(figaroh.tools._report_common) — a structured pass/fail against per-metric thresholds, with provenance (git commit, config hash, timestamp).verify(thresholds=None)andexport_verification_report(output_path=None, output_dir="results", thresholds=None)added to bothBaseCalibrationandBaseIdentification. Default thresholds cover condition number and, when validation data is configured, validation-set RMSE/correlation/ improvement — every threshold is overridable per call. A threshold whose metric isn't computable is skipped, not failed.- Called explicitly (not from inside
solve()) — verification is opt-in output computed from already-stored results. feat(reporting): Interactive before/after panel embedded in both HTML reports — a zoomable, hoverable overlay of nominal vs. fitted vs. measured values, rendered as hand-rolled inline SVG (no new dependency).feat(reporting):figaroh.tools.compare_report.generate_compare_page()— a static, self-contained, offline HTML page that loads two exported verification JSON files client-side and renders a per-metric diff table plus an overlaid before/after chart, gated by a mandatory compatibility check (domain, joint/DOF names,decimate, sample count) with an explicit "compare anyway" override on mismatch. No backend, no run history.fix(results_manager):plot_identification_results()reshaped 1D torque arrays asreshape(-1, 1), silently collapsing a multi-joint, joint-major-flattened array into a single mislabeled trace. Now accepts explicitn_joints/joint_namesand reshapes joint-major (reshape(n_joints, -1).T) when provided.refactor(results_manager):plot_with_fallback()helper deduplicates the four near-identical try/except plotting blocks acrossBaseCalibration,BaseIdentification,BaseOptimalCalibration, andBaseOptimalTrajectory.
Fixed¶
fix(calibration):BaseCalibration.calc_stddev()computed the calibrated-parameter count (self.nvars) once in__init__, beforeinitialize()populated the actual parameter list — leaving it stuck at 0 and silently producing emptystd_dev/std_pctglists. The "Parameter uncertainty" section was therefore always empty in both the terminal and HTML calibration reports. Now derives the count from the solved parameter vector (len(result.x)) at computation time.fix(identification):_compute_validation_metrics()'s before/after torque chart sliced the firstn_activejoint-blocks off the full-model regressor, which is only correct when the active/identified joints are exactly DOFs0..n_active-1. For robots identifying a strict subset of joints (e.g. TIAGo: torso+arm out of many more DOFs), this silently returned the wrong joints' nominal/identified torque predictions — visible as identical nominal/fitted curves and wildly wrong magnitudes in the before/after chart. Now indexes by the actualact_idxvDOF indices, mirroring the row-selection already used correctly in_decimate_regressor_matrix.fix(identification):_apply_weighted_least_squares()sliced the stacked regressor intomodel.nvequal blocks to estimate per-joint residual variance, which is only correct when every model joint is identified. For subset-identified robots (TIAGo:model.nv=24, 8 joints identified), this misaligned the slicing entirely, producing an RMSE ~2000x worse than plain OLS. Now useslen(identif_config["act_idxv"]), the same quantity_decimate_regressor_matrixalready uses.fix(identification):_prepare_undecimated_data()flattened torque data sample-major while regressor rows are joint-major, corruptingsolve(decimate=False)'s correlation/RMSE (0.44 vs 0.966 on identical data pre-fix).fix(reporting): the before/after HTML panel's joint/DOF dropdown rendered its options twice — once server-side in the generated HTML, once again client-side via JS — so every entry appeared duplicated in the selector.fix(reporting): the before/after HTML panel embedded the full validation-set series inline with no cap; for large validation sets (e.g. a fallback-to-full-training-data run with ~45k samples) this produced multi-MB HTML pages whose SVG path strings were too large to render reliably in the browser. Now uniformly subsampled to at most 3000 points, with the displayed sample count disclosed in the chart caption.fix(export_validation): the interactive viser GUI's speed/pose/opacity sliders crashed withAttributeError: 'GuiEvent' object has no attribute 'value'on every drag —GuiEventexposes the changed control via.target, not.valuedirectly.
Full design history and rationale:
docs/decisions/external-tool-comparisons.md
Part C.
[0.4.4] - 2026-06-27¶
Added¶
feat(calibration): Add validation data support for ground-truth FK testing._load_validation_data(path)— load a separate validation measurement CSV._compute_validation_metrics()— compare nominal vs calibrated FK against held-out measurements; reports position/orientation RMSE, max error, and improvement percentage.- Validation data path configurable via YAML (
validation_data_file) or CLI (--validation-data) in example scripts. feat(calibration): Add comprehensive statistical quality report.print_quality_report()— formatted terminal table with convergence status, per-DOF residuals (X/Y/Z/rx/ry/rz with R²), overall RMSE, validation metrics, parameter uncertainty (top 5), and strongly correlated pairs (|ρ| > 0.8)._compute_per_dof_stats()— mean, std, RMSE, max, R² per kinematic DOF._compute_condition_number()— condition number of Jacobian with well/moderate/ill-conditioned label._compute_parameter_correlation()— flags parameter pairs with |ρ| > 0.8 from the covariance matrix.- Report printed automatically after
solve()completes. feat(calibration): Addposition_frame='body'|'world'option to_compute_logmap_residuals()— choose body-frame or world-frame position error.feat(urdf_exporter): New modulefigaroh.tools.urdf_exporterfor applying calibrated joint parameters to URDF files with 12 joint-level categories.feat(export_validation): New modulefigaroh.tools.export_validationwithURDFComparisonclass — FK consistency checks, interactive viser visualization (trajectory animation, static overlay, error plots, replay controls).feat(backends): Multi-simulator backend architecture.PinocchioBackend— 371 lines wrapping all Pinocchio dynamics/kinematics calls.MuJoCoBackend— 441 lines with URDF→MJCF auto-conversion.DynamicsBackendabstract interface with 9 abstract + 6 optional methods.RobotIdentificationSystemhigh-level integration API.feat(tests): Cross-backend validation suite, backend unit tests (32 Pinocchio- 13 MuJoCo), TIAGo integration test fixture, URDF exporter tests.
docs: Combined ROADMAP.md (Track A + Track B), comprehensive architecture docs, ADR for calibration validation plan.
Fixed¶
fix(calibration): Replace element-wise RPY subtraction with proper SE3 log-map pose error in_compute_logmap_residuals()— geometrically correct orientation residuals without singularity issues.fix(backends): MuJoCoBackend regressor (delegates to Pinocchio analytical), Coriolis matrix (finite-difference Jacobian/2), mass matrix (adds missingmj_forwardcall).
Changed¶
breaking(export_validation): Renametrajectory_errors()→fk_consistency_check()andTrajectoryErrors→FkConsistencyResult— clarifies this is a file-integrity check, NOT ground-truth FK validation.chore: Add black formatter to pre-commit hooks.chore: Modernize pyproject.toml with dev dependencies and tool configs.
Tests¶
- 293 passed, 20 skipped, 0 failed (from 208 passed with 4 pre-existing failures).
- New:
test_backends.py(45 tests),test_cross_backend.py(integration),test_tiago_fixture.py(287 lines),test_urdf_exporter.py(296 lines),test_integration.py(250 lines).
[0.4.3] - 2026-06-02¶
Added¶
feat(cad_constraints): New modulefigaroh.identification.cad_constraintswith convex CAD-informed constraints for inertial parameter identification.CADConstraintsdataclass — container for per-link mass bounds, first-moment (CoM) bounds, and symmetry equality constraints.add_mass_bounds(cad, joint, *, m_min, m_max)— add box constraintm_min ≤ m_j ≤ m_maxfor a link.add_com_bounds(cad, joint, *, axis, h_min, h_max)— add first-moment (linear) box constrainth_min ≤ h_k ≤ h_maxfor axisk ∈ {x,y,z}.add_symmetry_constraints(cad, joint_a, joint_b, *, keys)— add equality constraints between inertial parameters of two symmetric links.bounds_from_urdf(model, ...)— derive mass and CoM bounds automatically from Pinocchio model URDF inertials (CAD data source strategy).build_cad_constraints_from_config(cfg, *, model)— buildCADConstraintsfrom a YAML config sub-dict; returnsNonewhen empty (safe default-off).apply_cad_constraints_to_problem(problem, theta, params_r, cad)— inject CAD constraints into a picosProblemfor the full-robot SDP.feat(physical_consistency):project_p10_lmigains optionalmass_boundsandcom_boundskwargs — per-link box constraints on the SDP.feat(physical_consistency):project_robot_p10_lmigains optionalcad_constraintskwarg — passes per-link bounds toproject_p10_lmi.feat(reconstruction):_reconstruct_sdpandreconstruct_full_parametersgain optionalcad_constraintskwarg — injects CAD constraints after the per-joint LMI loop in the full-robot SDP.feat(base_identification): Both_apply_physical_consistency_if_enabledand_apply_reconstruction_if_enabledhooks parsecad_constraintsfrom config and pass the result to the underlying solvers.- Example YAML comment block for
cad_constraintsinfigaroh-examples/examples/templates/manipulator_robot.yaml.
Changed¶
- All new parameters are optional with safe defaults — fully backward-compatible.
[0.4.2] - Unreleased¶
Added¶
feat(reconstruction):ReconstructionResult.status— outcome code ("ok","solver_missing","error") on every resultfeat(reconstruction):ReconstructionResult.base_residual_norm— L2 norm of the base constraint residualM θ − φ_base(scalar, always set)feat(reconstruction):ReconstructionResult.objective— SDP objective value for Option B (Nonefor nullspace)feat(reconstruction):BaseResultfrozen dataclass — structured container for(M, phi_base, params_r)as input toreconstruct_full_parameters()feat(reconstruction):reconstruct_full_parameters()— unified entry point withmethod="nullspace" | "sdp" | "auto"dispatchfeat(reconstruction): Option B SDP (method="sdp") — minimises‖diag(w)(θ−θ₀)‖²subject toM θ = φ_baseand per-joint pseudo-inertia LMIP_j ≽ 0via Schur-complement epigraph (requires picos + cvxopt/mosek)feat(reconstruction):method="auto"— silently falls back to nullspace when picos is not installed;status="solver_missing"when picos unavailable andmethod="sdp"was requested explicitlyfeat(reconstruction):_load_prior_from_urdf()— builds flat prior dict from Pinocchio model inertias (prior_source="urdf")feat(reconstruction):_load_prior_from_yaml()— loads prior from a flat YAML file (prior_source="yaml")feat(identification):_apply_reconstruction_if_enabled()hook inBaseIdentification._store_results()— called after physical consistency; stores result underself.result["reconstruction"]feat(identification):_calculate_base_parameters()now usesQRDecomposerdirectly to exposeself._M_matrix/self._params_r_for_reconfor downstream reconstruction; result dict includes"M"and"params_r"keysfeat(config):reconstructionblock parsed from both legacy YAML (get_param_from_yaml) and unified config (unified_to_legacy_identif_config)feat(identification/__init__):BaseResultandreconstruct_full_parametersadded to public exports and__all__tests: 13 new unit tests intests/unit/test_reconstruction.pycovering new fields, BaseResult, prior helpers,_p10_indices_for_joints, nullspace end-to-end, auto fallback, YAML prior, and unsupported method error
Fixed¶
fix(reconstruction): alternation loop inrun_reconstructionnow correctly unpacksproject_robot_p10_lmi()as(projected_p10_dict, robot_report); removesAttributeError: tuple has no attribute 'p10_by_joint'fix(identification):"projected parameters"key (space) renamed to"projected_parameters"(underscore) in_apply_physical_consistency_if_enabledresult dict for consistency with"raw_parameters"and Python conventionsfix(tools/robotcollisions):print_collision_pairs()usesprint()instead oflogger.info()so output is visible in interactive use and captured by testsfix(tools/solver):LinearSolver._print_solution_info()usesprint()instead oflogger.info()so verbose output is visible in interactive use and captured by tests
[0.4.1] - Unreleased¶
Added¶
feat(physical-consistency):is_feasible_link()— public alias forcheck_p10_feasibility()matching roadmap spec namingfeat(physical-consistency):project_link()— public alias forproject_p10_lmi()matching roadmap spec namingfeat(physical-consistency):ProjectionReport.runtime— per-link solve time (seconds) recorded viatime.perf_counter()aroundproblem.solve()feat(physical-consistency):weightskeyword argument toproject_robot_p10_lmi()for passing a 10-element weight vector to all per-link projection callsfeat(config):weights.mode: "auto" | "manual"parsed from thephysical_consistencyYAML block in_apply_physical_consistency_if_enabledfeat(config):weights.manual.{m, h, Sigma}per-group manual weights built into a 10-element array and forwarded toproject_robot_p10_lmifeat(identification):physical consistencyresult dict now stores bothraw_parameters(pre-projection) andprojected_parameters(post-projection) as separate keys, preserving the original identified values for comparisontests: 28 unit tests intests/unit/test_physical_consistency.pycovering TC-1 through TC-12 (projection correctness, aliases, runtime, robot aggregation) plus 3 config-wiring tests for weights and raw/projected separation
Fixed¶
fix(physical-consistency): SDP formulation inproject_p10_lmino longer uses the picos 2.x-incompatiblepc.vstack,pc.multiply, orpc.sum_squaresfunctions; replaced with element-wise objective usingpc.SquaredNormand explicit sigma-entry loopfix(physical-consistency): solver keywordverbose=replaced withverbosity=(picos 2.x API); eliminatesDeprecationWarningon every callfix(physical-consistency): feasibility check after projection uses a relaxedpsd_eig_tol=-1e-8tolerance to absorb inevitable SDP solver numerical noise, preventing valid projections from being reported as"infeasible"due to tiny eigenvalue violations (~1e-9)
[0.3.1] - 2026-04-01¶
Added¶
feat(physical-consistency): optional projection of inertial parameters onto a physically consistent set (identification/physical_consistency.py)feat(reconstruction): reconstruction utilities —run_reconstructionandrun_option_a_reconstructionentry points (identification/reconstruction.py)feat(qrdecomposition):get_M/get_M_labelsmethods for retrieving the stored base mapping matrix after decompositionfeat(qrdecomposition):QRResultdataclass for structured, full-precision decomposition output (rank, base_indices, W_b, beta, M, phi_b, diag_R, cond_R1, method, …)feat(qrdecomposition):relative_toleranceconstructor parameter for scale-invariant rank detection (threshold relative to largest pivot)feat(qrdecomposition):get_diagnostics()method returning rank, diag_R, cond_R1, and method after every decompositionfeat(qrdecomposition):decompose()unified entry point returning aQRResult; delegates to pivoting or double path based onmethodargumentfeat(identification): enhanced parameter management with standard and custom parameter support inBaseIdentification
Fixed¶
fix(qrdecomposition):_find_ranknow correctly returns 0 for zero/empty matrices (previously returned row count, causing phantom base parameters)fix(reconstruction): add missingrun_option_a_reconstructionalias that blocked package-level import
Changed¶
refactor(qrdecomposition): replace non-pivotednumpy.linalg.qrwithscipy.linalg.qr(..., pivoting=True)in_identify_base_parameters— permutation-stable, deterministic basis selectionrefactor(qrdecomposition): remove prematurenp.aroundfrombetain all code paths; rounding deferred to display-only_build_parameter_expressionsrefactor(qrdecomposition): remove fragileassert np.allclose(W_base, W_b)refactor(qrdecomposition): enhanced docstrings and nominal-parameter handlingrefactor(identification): patch filter configuration management inBaseIdentificationrefactor(calibration): update deprecatedFrame.parent→Frame.parentJointfor Pinocchio 3.x compatibilityrefactor(logging): replace print statements withloggingcalls across calibration and identification modulesrefactor(config): renameload_from_yaml→load_paramfor clarity
Tests¶
test(qrdecomposition):TestNumericalImprovementssuite — 14 new tests, 23/23 total pass (rank-zero, relative tolerance, permutation stability ×20, mapping matrix property, column space, diagnostics, QRResult structure, full-precision beta)test(reconstruction): unit tests for reconstruction utilities
[0.3.0] - 2025-12-09¶
Added¶
- Advanced Linear Solver (
figaroh.tools.solver): Comprehensive multivariate linear solver for robot parameter identification - Multiple solving methods: lstsq, QR, SVD, Ridge, Lasso, Elastic Net, Tikhonov, constrained, robust, weighted
- Regularization support: L1 (Lasso), L2 (Ridge), Elastic Net, custom Tikhonov
- Constraint handling: Box constraints (bounds), linear equality/inequality constraints
- Robust regression with iterative reweighting for outlier resistance
- Comprehensive solution quality metrics (RMSE, R², condition number, residuals)
- Optimized for dense, large, thin matrices typical in robot dynamics
-
Full unit test coverage (18 tests) with robot identification scenarios
-
Module Reorganization: Better code organization and separation of concerns
- Calibration module restructuring:
calibration/config.py: Configuration parsing and YAML handling (624 lines)calibration/parameter.py: Parameter management utilities (240 lines)calibration/data_loader.py: Data loading and I/O operations (160 lines)
- Identification module restructuring:
identification/config.py: Configuration parsing for identification (334 lines)identification/parameter.py: Parameter management for identification (388 lines)
-
Maintains 100% backward compatibility through re-exports
-
BaseIdentification Enhancement:
solve_with_custom_solver(): New method using advanced linear solver with regularization and constraints- Flexible solving with multiple methods and custom constraints
- Support for physical parameter bounds (e.g., positive masses/inertias)
Improved¶
- Parameter Ordering: Changed to Pinocchio dynamic parameter ordering for consistency
- New order: [Ixx, Ixy, Ixz, Iyy, Iyz, Izz, mx, my, mz, m]
-
Previous order: [m, mx, my, mz, Ixx, Ixy, Iyy, Ixz, Iyz, Izz]
-
Regressor Module: Cleaned up build_basic_regressor methods
- Removed unused
tauparameter for better API clarity -
Improved method signatures and documentation
-
Code Quality: Significant reduction in code duplication
calibration_tools.py: Reduced from ~1500 to ~630 lines (-58%)identification_tools.py: Reduced from ~900 to ~295 lines (-67%)- Modular design with clear single responsibilities
Fixed¶
- Parameter naming: Changed from numbered indices to parent joint names for clarity
- Regressor handling: Better support for additional columns in regressor matrices
- Results manager imports and formatting issues
Technical Details¶
- Files Changed: 21 files
- Lines Added: +3,372
- Lines Removed: -1,604
- Net Change: +1,768 lines
- Test Coverage: All 18 new solver tests passing
[0.2.4] - 2025-09-08¶
Changed¶
- Optional Dependencies: Removed
cyipoptfrom required dependencies - cyipopt is now truly optional and loaded only when IPOPT optimization is used
- Users can install without cyipopt and still use all other features
- Install cyipopt separately when needed:
pip install cyipoptor via conda environment
Improved¶
- Installation Flexibility: Package now installs without requiring heavy optimization dependencies
- Error Handling: Better error messages when optional dependencies are missing
[0.2.3] - 2025-09-08¶
Added¶
- Streamlined Dependencies: All core dependencies now available via PyPI with automatic installation
- Lazy Loading: Optional dependencies (cyipopt) now loaded only when needed to improve startup time
- Enhanced Installation Notes: Clear documentation of simplified dependency management
Improved¶
- Dependency Management: Complete cleanup and optimization of package dependencies
- Removed redundant
requirements.txtandsetup.pyfiles - Consolidated all dependencies in
pyproject.toml - Updated to use PyPI versions of robotics libraries (
pinfor Pinocchio) - Installation Process: Significantly simplified installation with better cross-platform compatibility
- Documentation: Comprehensive README updates reflecting modern packaging standards
- Combined development installation methods for clarity
- Added official Pinocchio repository reference
- Updated dependency documentation with descriptions
- Performance: Faster module loading through localized imports
- Environment Setup: Streamlined conda environment with minimal dependencies
Enhanced¶
- Import Strategy: Localized cyipopt import to specific functions for better error handling
- Error Messages: More informative import error messages with installation instructions
- Package Structure: Modern Python packaging standards with pyproject.toml-only approach
Removed¶
- Redundant Files: Eliminated
requirements.txtandsetup.pyin favor of modernpyproject.toml - Unnecessary Dependencies: Cleaned up unused dependencies for leaner installation
Fixed¶
- Package Name: Corrected dependency references (e.g., proper use of
pinfor Pinocchio PyPI version) - Installation Conflicts: Resolved potential conflicts between conda and pip installations
[0.2.0] - 2025-09-05¶
Added¶
- Unified Configuration System: Complete overhaul of configuration management
- New
UnifiedConfigParserwith YAML template inheritance - Automatic format detection for seamless legacy compatibility
- Advanced parameter mapping between configuration formats
-
Comprehensive configuration validation with helpful error messages
-
Enhanced Base Classes: Modern object-oriented workflow management
BaseCalibration: Standardized calibration workflow with unified config supportBaseIdentification: Standardized identification workflow with unified config support-
Automatic configuration format detection and conversion
-
Advanced Regressor Builder: Complete redesign of regressor computation
RegressorBuilder: Object-oriented, extensible regressor constructionRegressorConfig: Configuration dataclass for regressor parameters- Enhanced input validation and error handling
-
Support for joint torque and external wrench modes
-
Configuration Format Mapping: Seamless format conversion utilities
unified_to_legacy_config: Calibration parameter mapping functionunified_to_legacy_identif_config: Identification parameter mapping function- Perfect compatibility with existing legacy configurations
Improved¶
- Parameter Processing: Enhanced parameter handling with better defaults
- Error Messages: More informative validation and error reporting
- Documentation: Comprehensive updates to README and module documentation
- Code Organization: Better structured modules with clear responsibilities
- Type Safety: Added type hints throughout the codebase
Enhanced¶
- Cross-Platform Support: Improved compatibility across operating systems
- Input Validation: Robust parameter validation and type checking
- Template System: Flexible configuration template inheritance
- Backward Compatibility: Full support for existing legacy configurations
Removed¶
- quadprog dependency: Removed unused quadprog dependency to reduce package size
Fixed¶
- Configuration Parsing: Resolved edge cases in YAML parsing
- Parameter Mapping: Accurate conversion between configuration formats
- Validation Logic: Improved configuration validation accuracy
- Error Handling: Better error recovery and user feedback
Technical Improvements¶
- Modern Python practices with dataclasses and type hints
- Enhanced error handling with custom exception classes
- Improved testing framework with comprehensive validation
- Better code documentation and examples
Documentation¶
- Updated README with modern API examples
- Enhanced configuration system documentation
- New API usage patterns and best practices
- Comprehensive module documentation updates
[0.1.0] - 2025-01-25¶
Added¶
- Initial release of FIGAROH package
- Dynamic identification algorithms for rigid multi-body systems
- Geometric calibration algorithms for serial and tree-structure robots
- Support for URDF modeling convention
- Optimal trajectory generation for dynamic identification
- Optimal posture generation for geometric calibration
- Integration with Pinocchio for efficient computations
- Support for various optimization algorithms
- Data filtering and pre-processing utilities
- Model parameter update utilities
Features¶
- Dynamic Identification:
- Dynamic model including friction, actuator inertia, and joint torque offset
- Continuous optimal exciting trajectory generation
- Multiple parameter estimation algorithms
-
Physically consistent standard inertial parameters calculation
-
Geometric Calibration:
- Full kinematic parameter calibration
- Optimal calibration posture generation via combinatorial optimization
- Support for external sensors (cameras, motion capture)
- Non-external methods (planar constraints)
Dependencies¶
- Core scientific computing: numpy, scipy, matplotlib, pandas
- Robotics: pinocchio (via conda)
- Optimization: cyipopt (via conda)
- Visualization: meshcat
- Additional: numdifftools, ndcurves, rospkg
Documentation¶
- Comprehensive README with installation and usage instructions
- Examples moved to separate repository (figaroh-examples)
- API documentation structure prepared
Notes¶
- Examples and URDF models moved to separate repository for clean package distribution
- Package optimized for PyPI distribution
- Supports Python 3.8+