Skip to content
Copy as Markdown

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.txt and llms-full.txt (mkdocs-llmstxt, added to the docs extra) and every page has a Copy as Markdown button.
  • README screenshot of the documentation landing page.

Changed

  • pre-commit check-yaml runs with --unsafe so mkdocs.yml can use mkdocs-material !!python/name tags.

[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.py regenerates them (#198).

Changed

  • Copyright notices updated to 2026 for the maintainer and corrected to 2022–2024 for CNRS/Toward SAS; CONTRIBUTING.md release step 2 now checks that the notices include the release year (#197).
  • ROADMAP.md records 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 and from_mjcf, integration, visualisation, measurements, log-Cholesky LC1), the internal modules, the deprecated features and the deprecation policy: a public name warns with DeprecationWarning for 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 figaroh also imports data, backends and integration (about 0.1 s more, mostly the optional mujoco import when it is installed). calibration.__all__ adds calibration_tools, data_loader and parameter; identification.__all__ adds identification_tools, parameter and physical_fit; tools.__all__ adds run_archive, stages and solver; utils.__all__ adds config_parser and error_handling; optimal.__all__ adds BaseTrajectoryIPOPTProblem; 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_pivoting and double_QR: use QRDecomposer.decompose_with_pivoting / double_decomposition.
  • figaroh.tools.robotcollisions.CollisionWrapper: use CollisionManager.
  • 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 the RobotVisualizer methods.
  • plot_calibration_results, plot_identification_results and save_results in figaroh.utils.results_manager: use ResultsManager.
  • The flat physical_consistency_enabled identification config key: use physical_consistency.enabled.

Changed

  • requires-python is now >=3.10, following Pinocchio (conda-forge builds 3.8 and 4.1 for Python 3.10-3.14; pin 4.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_parameters also reads a data_dir stored under tasks.identification of a unified config, since the flat identification: 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_yaml or the optimal-trajectory loader now raises a ConfigurationError that names python -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, both get_param_from_yaml_legacy, and the get_param_from_yaml re-exports of calibration_tools and identification_tools. Use figaroh.utils.config_parser.get_param_from_yaml with a unified config.
  • get_param_from_yaml_with_warning in figaroh.calibration.config and figaroh.identification.config (since 0.2.0): call figaroh.utils.config_parser.get_param_from_yaml.
  • figaroh.utils.config_parser.get_calibration_param_from_yaml and get_identification_param_from_yaml (since 0.2.0): call get_param_from_yaml(robot, data, "calibration") or get_param_from_yaml(robot, data, "identification").
  • A non-zero regularization_coefficient / coeff_regularize (since 0.6.0) is a ConfigurationError. Remove the key (zero or absent is still accepted) and use estimation.method: map, which sets priors of physical size per parameter group. config_migration writes 0 and warns when it meets a non-zero legacy coeff_regularize.

Fixed

  • A calibration whose solver failed can no longer be exported silently (#182, M1 G-1). BaseCalibration.STATUS is "FAILED" instead of "CALIBRATED" when the solver did not converge, and joint_corrections() (the URDF export gateway), build_geometric_calibration and export_geometric_calibration_yaml raise CalibrationError when the fit stage is failed, recording stage export as failed, as BaseIdentification.export_urdf already does. allow_failed_fit=True exports for diagnosis: it warns, records export as fallback and notes the override in the run provenance. A solver failure on the first fit now returns a failed result instead of raising an UnboundLocalError. 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_base is 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 reconstruction enabled it is skipped and the physical stage is not_run; it was recorded ok.
  • identification.select_stage and problem.torque_fit_joints are 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_stage none, verify fails, export refuses). The default is unchanged. New BaseIdentification.export_urdf, and merged_bodies="subtract_fixed" in the URDF exporter. The direct LMI effort fit is public as figaroh.identification.physical_fit (_physical_comparator stays as an alias); ReconstructionResult.effective_method is new. (#61)
  • problem.torque_fit_joints names 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 under not_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.md for 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 in CONTRIBUTING.md now includes revising both READMEs.

Fixed

  • Package metadata declares the license as Apache-2.0 (an SPDX License-Expression, with LICENSE as the license file), matching LICENSE. It declared a BSD classifier and copied the whole license text into the License field, 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 reconstruction enabled the block is skipped and the physical stage is not_run; it was previously recorded ok for a projection of the CAD prior. (#163)

  • A configured max_seconds in the SDP reconstruction and the LMI projection is passed to picos as timelimit; 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. QRDecomposer picks base columns with the new deterministic_column_pivots: largest residual first, columns within a relative 1e-6 of the largest are tied and the smallest parameter name wins. phi_base is 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's idx_q and velocities and accelerations through its idx_v, labelled with the joint names. ResultsManager.plot_optimal_trajectory_results gains q_indices and v_indices; without them it plots every column and refuses (logged error) when nq != 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_coefficient is deprecated and defaults to 0; use estimation.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_params and joint_offset semantics (#101, #110).
  • export_urdf refuses 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.markers entry 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_fkm no longer raises for NbMarkers > 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_urdf writes identified standard inertial parameters (m_, mx_…mz_, Ixx_…Izz_, Pinocchio toDynamicParameters() convention) as URDF mass, centre of mass and centre-of-mass tensor (parallel-axis theorem), keeping an existing inertial rpy. Targets may be links or moving joints (as identification names them). Partial sets, joints whose Pinocchio body merges fixed-attached massive links, m <= 0 and, unless allow_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_metrics gains per_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) and correlation_normalised (correlation of per-joint standardised signals). The verdict's validation_correlation now uses the normalised value, and validation_unpredictable_joints counts flagged joints; the pooled correlation is 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.stages gains data, fit, validation (fallback on training data, never held-out evidence), physical and export beside 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 under stage_records.

  • figaroh.data: data contract types from the accepted decision record docs/decisions/data-result-contract.md (#55, part 1 of 2; additive).

  • TrajectoryData (identification): time with a recorded/assumed clock, joint names, per-signal origin, effort in its recorded units with a per-joint kind and unit (joint_torque N·m, joint_force N, 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_legacy reproduce load_data exactly, with deleted rows masked instead, and leave calib_config unchanged.
  • DataSource and Session (files with sha256, adapter, recording identity) and Protocol (versioned roles per session, checked against the files' sha256).
  • Data contract: TrajectoryData.sample_index / PoseObservations.sample_index record each sample's source row (default 0..n-1; from_csv sets CSV rows, so masked postures are named by row), and PoseObservations.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 a TrajectoryData. 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 sample k is sample k * factor). A subclass returning one must convert the effort with TrajectoryData.converted(), not override process_torque_data(). Legacy dicts work as before.
  • BaseCalibration.load_data_set() reads the CSV through PoseObservations (same arrays as load_data); del_list rows are masked, and calibrator.observations keeps every posture.
  • figaroh.tools.stages: each run records data, fit, validation (fallback when evaluated on training data), physical and export (failed when joint_corrections() refuses parameters) stages on obj.stages, in the results dict under "stages", in the run archive's stages.json, and in the verification JSON, which also gains schema_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 default structural is unchanged.

  • excitation: select over every joint parameter on the measured postures and drop those whose predicted standard error exceeds excitation_k times their expected size.
  • map: estimate every joint parameter with Gaussian priors of the expected sizes (priors); posterior standard deviations in std_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 in docs/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 appended sqrt(c) * theta rows for every joint parameter, so a metre and a radian were penalised alike. Core never applies the coefficient; a non-zero value now raises a DeprecationWarning pointing to estimation.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_calibration export (build_geometric_calibration, export_geometric_calibration_yaml) fixed and given nominal_urdf (#123).
  • Joint offsets exported: joint_offset-level corrections (offsetRZ_* etc.) were dropped, so the TIAGo reference wrote an empty geometric_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_urdf refuses what it cannot write instead of skipping it (#62). A correction for a joint or link missing from the URDF, inertia tensors, first moments, legacy off_*, and a joint with both offset* and d_* now raise ValueError; no file is written. joint_corrections() raises CalibrationError for fitted parameters a URDF cannot carry (elastic, contact planes); pass drop_unsupported=True to 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 PAL geometric_calibration export and the HTML report) lifts structural base 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.priors weight 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 structural change.

  • 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_list builds 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 in calib_config["absorbed_param_name"]. TIAGo mocap: joint_offset drops the torso and arm_1 (rank 14/14 instead of 14/16); full_params drops the tool-absorbed d_pz_arm_7_joint (with #110; before it, 4–9 parameters including an RPY near-singularity).
  • Closed-form start: solve_optimisation starts unknown base/tip frames at estimate_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"] = False for the previous set.
  • full_params placement errors d_{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_placement added d_p* to the parent-frame translation and d_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_urdf applies a joint's six d_* values together as origin · SE3(exp3(d_phi), d_p); build_geometric_calibration converts them to PAL origin xyz/RPY deltas against the nominal placement.
  • Behaviour change: full_params values from earlier versions use the old convention and are not comparable on rotated placements (UR10, Talos, TIAGo). joint_offset is 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 (srdf removes pairs). Collision was checked at waypoints only, so splines crossed obstacles between them; initial guesses and every solved segment must now keep collision_margin along the whole spline (collision_check_frequency, default 200 Hz), and collision_checks_per_interval adds check points inside the constraint. collision_screen computes 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). QRDecomposer rounded phi_b to 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, legacy random_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 configured parameters.outlier_threshold was unused. Samples whose position error exceeds outlier_threshold (metres, outlier_eps; rotational components do not take part) are now excluded from the fit and the rest refitted, for up to max_iterations fits. solve(outlier_threshold=...) now overrides the config in metres (default None: 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 adds excluded_samples, excluded_sample_errors and outlier_threshold, and the terminal and HTML reports list them. The fit stage metric flagged_outliers is now excluded_outliers.

  • Calibration quality reports label residual rows from measurability (#100). A contact calibration measuring [z, roll, pitch] was printed as X/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), and verify() picks position_rmse_mm/orientation_rmse_deg from 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_dof in 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_fkm with an empty parameter list (nominal FK) raised UnboundLocalError; 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_joint searched 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 behind redistribute_parameters) were ~100× too small (#107).

  • Cause: calc_stddev estimated the residual variance as result.cost**2 / (m - n), but least_squares' cost is already 0.5 * sum(fun**2).
  • Fix: it now uses sum(r**2) / (m - n) over the measurement residuals, excluding regularisation rows a subclass cost_function appends. 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_params corrections instead of all 41.
  • add_base_name shifts base_mapping_slice when it prepends the base names at joint_offset level; the slice pointed six entries too early.
  • Calibration held-out data (validation_data_file) works (#105).
  • Config key read: tasks.calibration.data.validation_data_file is now read from the unified config. Before, only a CLI override reached calib_config.
  • Training count preserved: loading the validation CSV no longer overwrites the training calib_config["NbSample"]. A larger validation set crashed solve(), and a smaller one silently truncated training.
  • Unpack order fixed: _load_validation_data no longer swaps poses and joint configurations.
  • All samples used: validation metrics now evaluate every validation sample (_compute_logmap_residuals takes n_samples).
  • Failures logged: a validation file that fails to load now logs a warning instead of being skipped silently.
  • Calibration joint_offset parameters (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. New calibration_tools.apply_joint_offset composes 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_offset results 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_urdf writes joint offsets into the joint <origin> (about the joint's own axis), so the reloaded URDF reproduces q + 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 Lint job runs every pre-commit hook on the full tree instead of only on changed files, and the advisory Lint backlog job 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() without scope now means prediction acceptance, so callers that only checked .passed get False (not evaluated) unless they supply separately loaded validation data and limits; pass scope="execution" for numerical-execution checks. The default thresholds are now empty, and training-data fallback metrics are named training_*.

  • 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 devel integration / main release 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 qrdecomposition uses 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_torque calls 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.
  • RobotIPOPTSolver no 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 marked converged: False with a warning. The iteration cap is configurable as problem.max_iterations (default 200).
  • Configuration variants that extends a 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 with calibration: / 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 nv rather than nq or 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_fullM signatures: the pre-3.10 (model, destination, qM) form and the newer (model, data, destination) form. This fixes failures with recent MuJoCo versions that removed data.qM.

  • get_standard_parameters read each joint's CAD inertia from the body before it (model.inertias[i] instead of [i + 1]), so tau_ref, the nominal validation torque/improvement_pct and 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" raised IndexError. add_standard_additional_parameters now only emits enabled blocks, so parameter keys always line up with regressor columns. With friction only, the parameter dict no longer carries unused Ia_*/off_* keys. (#12)
  • Unified config: signal_processing.sampling_frequency, cutoff_frequency and filter_order now default the filter's f_sample/f_butter/nbutter, which were stuck at 100 Hz / 2 Hz / 4. Explicit filter_params still win. A cutoff at or above Nyquist raises ValueError. (#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: New skills/ 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 in figaroh-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 from examples/<robot>/; package_dirs="../../models"), and dispatches to the skill below.
  • figaroh-setup-env — environment bootstrap, with an executable scripts/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 — whether import figaroh resolves to the local src/ 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 from calibration/data_loader.py and each robot's load_trajectory_data() rather than from prose, because both are derived rather than fixed: calibration CSV columns follow each marker's measurable_dof, and identification has no single schema at all.
  • figaroh-setup-new-robot — onboarding a robot with no example folder, from description package to validate.py --robot <robot> exiting 0. The set is self-contained by design: it assumes only figaroh and figaroh-examples, references no other package or checkout, and points at repo directories rather than at skills outside the set.
  • docs: README now points at skills/ for agent-assisted setup.

Fixed

  • tools: build_total_regressor_wrench raised NameError on every call. Its signature named the settings dict param while the body read identif_config three times -- an incomplete rename that made the exported function unusable. Renamed the parameter to identif_config, matching both the body and the sibling build_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.py now covers it (7 tests, verified to fail if the rename is reverted).
  • optimal: BaseOptimalCalibration.save_results reported configuration_count as len(self.optimal_configurations), which counts the dict's keys (always 2) rather than the selected configurations. A UR10 run wrote configuration_count: 2 to the summary CSV while printing "Selected 73" to stdout. Added count_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 key condition_number, which is a different quantity entirely and made the CSV actively misleading. Renamed to d_optimality_detroot. Breaking for anything reading that column out of a saved optimal-calibration CSV/YAML.
  • optimal: tasks.optimal_configuration.output.output_file was silently ignored — load_param only ever loads the calibration task, so the optimal task's own output block never reached the object, and save_results hardcoded results/. The configured directory is now honoured (the filename stays managed/timestamped), falling back to results/ 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 YAML None-as-string trap, legacy configs, and missing files). save_results previously had no coverage.
  • tests/unit/test_regressor_wrench.py: 7 new tests calling build_total_regressor_wrench for real — output shapes, finiteness, mass_load scaling, and which_body_loaded block selection. Verified to fail with the original NameError if the fix is reverted.

Changed

  • build: Pinned the build backend to hatchling<1.32. 1.32.0 emits Metadata-Version: 2.5, which the current release toolchain rejects -- twine check fails with "'2.5' is not a valid metadata version" because packaging 25.0 recognises only up to 2.4. Every hatchling through 1.31 emits 2.4. requires was previously unpinned, so each build silently took whatever was newest; this also makes release builds reproducible.
  • docs: Updated README's Examples Repository table with recent figaroh-examples additions: 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: Split docs/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 in figaroh-examples PR #9, with an implementation summary, concrete real-data results, and known gaps against the original plan) and modular-linear-residual-terms-plan.md (still proposed — the generic LinearRegressorTerm/ResidualTerm/WeightPolicy architecture and the unbuilt physical/stateful backlash model). Also corrected ROADMAP.md §8.2's stale "not started" status for the suspension port (superseded by the new document) and added both new documents to ROADMAP.md's References table and docs/source/further_reading/decisions.md's index.
  • docs: Consolidated docs/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_calibration comparison
    • calibration-composability adaptation roadmap), Part B (MuJoCo sysid comparison), Part C (the full reporting/verification infrastructure build-out that Part B's comparison produced).
  • 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: Rewrote ROADMAP.md (v2.0 → v2.1) as a single coherent document: added a table of contents, a References section linking every roadmap track to its source docs/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/RobotIdentificationSystem is 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 to ROADMAP.md, explicitly marked as ongoing research rather than a committed track. Sourced from a new docs/decisions/sim2real-modelbased-deployment.md positioning 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, via figaroh.tools.qrdecomposition.redistribute_min_norm/ propagate_covariance_min_norm (Moore-Penrose pseudoinverse of the base-mapping matrix M, phi_base = M @ theta_r). Previously, calculate_base_kinematics_regressor already 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 existing solve()/report behavior. See docs/decisions/tiago-calibration-and-port-review.md Part 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 above redistribute_parameters() on the live calibrator and rendering it through the same _param_uncertainty_section table 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 Robotics robot_state_publisher.geometric_calibration runtime-correction YAML (the format hand-curated to date in figaroh_tiagoPro/data/master_calibration_*.yaml) directly from a solved BaseCalibration instance's redistributed parameters, with an optional min_sigma threshold reproducing the "conservative" (≥2σ) deploy variant. Reuses urdf_exporter._parse_param_name for joint/axis parsing; excludes the co-estimated-base-merged parameter block structurally (via base_mapping_row_names) rather than by name pattern, so it's robust to whatever display renaming a BaseCalibration subclass applies to calib_config["param_name"].
  • feat(examples): tiago, tiago_pro, ur10, and talos example calibration scripts now export master_calibration.yaml (full) and master_calibration_conservative.yaml (≥2σ) to the run directory by default alongside report.html, via the new geometric_calibration_export module — new --geometric-calibration-yaml CLI flag (default on) to opt out, mirroring the existing --html-report flag.
  • 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_section gained an optional values column, and BaseCalibration's stored evaluation metrics gained param_values (result.x) alongside the existing param_stdev/param_stddev_percentage so the report has a value to show. The identification report's base-parameter table picks up the same column for free (phi_base was already computed there, just not threaded through to the table). Backward compatible: a caller/older run without param_values renders "—" instead of failing.

[0.4.6] - 2026-08-06

Fixed

  • fix(calibration): calc_updated_fkm now 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 in figaroh/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 key left 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_transform without re-running forward kinematics after perturbing model.jointPlacements for the deflection, so the elastic offset never actually reached the output pose — fixed by re-running FK before computing oMee.
  • calc_updated_fkm previously silently ignored calib_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 > 1 now raises NotImplementedError instead 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 existing full_params/known_baseframe=False geometric path is numerically unchanged (bit-identical output for a fixed parameter set and data).
  • fix(config): unified_to_legacy_config read parameters.free_flyer, a key the unified schema never defines (the template puts it at kinematics.free_flying_base) — a unified config setting free_flying_base: true was silently ignored, always resolving to free_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 reserved tasks.calibration.eye_hand.{camera_frame,reference_frame} for it — unified_to_legacy_config just never read that section. Added _extract_eye_hand_params, wired in.
  • fix(calibration): BaseCalibration reported RMSE/MAE two different ways that could disagree by exactly sqrt(n_dofs) depending on which number you looked at — _evaluate_solution's evaluation_metrics["rmse"]/["mae"] flattened every x/y/z/... residual component into one list (a "mean of squared components" convention), while _compute_per_dof_stats's pos_rmse_mm/orient_rmse_deg and the validation table reduced each sample's residual vector to one Euclidean-norm distance first, then aggregated. For position-only calibration these differed by exactly sqrt(3), always. Converged _evaluate_solution, _detect_outliers, and _store_optimization_results's _PEE_dist onto 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 added pos_mae_mm/orient_mae_deg to _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() in BaseCalibration, but correlation reads self._C_param, which calc_stddev() is what sets — on a fresh instance's first solve(), correlation silently returned [] instead of the actual pairs. Reordered so calc_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 a DeprecationWarning (+ logger.warning) from figaroh.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 --urdf flag runs a round-trip self-check (converts, then re-parses through the real unified_to_legacy_*config and diffs against the original) rather than trusting the conversion blindly. Fields with no unified-format consumer today are preserved under custom: instead of silently dropped. See "Migrating from legacy to unified format" in docs/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 and verify() verdict reflect the WLS-refined parameters rather than a stale OLS estimate. Config-driven via identification.problem.wls in the unified YAML config, following the existing has_friction/has_joint_offset pattern.
  • feat(tooling): figaroh.tools.provenance and figaroh.tools.run_archive — every calibration/identification run now carries a provenance record (git commit, config hash, timestamps, robot/asset identity), and archive_run() writes each run to a timestamped results/runs/<robot>/<task>/<timestamp>/ directory (config snapshot, provenance JSON, report) instead of overwriting a single results/ 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 existing plotting/save_results flag 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 via identification.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) and export_verification_report(output_path=None, output_dir="results", thresholds=None) added to both BaseCalibration and BaseIdentification. 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 as reshape(-1, 1), silently collapsing a multi-joint, joint-major-flattened array into a single mislabeled trace. Now accepts explicit n_joints/joint_names and 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 across BaseCalibration, BaseIdentification, BaseOptimalCalibration, and BaseOptimalTrajectory.

Fixed

  • fix(calibration): BaseCalibration.calc_stddev() computed the calibrated-parameter count (self.nvars) once in __init__, before initialize() populated the actual parameter list — leaving it stuck at 0 and silently producing empty std_dev/std_pctg lists. 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 first n_active joint-blocks off the full-model regressor, which is only correct when the active/identified joints are exactly DOFs 0..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 actual act_idxv DOF indices, mirroring the row-selection already used correctly in _decimate_regressor_matrix.
  • fix(identification): _apply_weighted_least_squares() sliced the stacked regressor into model.nv equal 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 uses len(identif_config["act_idxv"]), the same quantity _decimate_regressor_matrix already uses.
  • fix(identification): _prepare_undecimated_data() flattened torque data sample-major while regressor rows are joint-major, corrupting solve(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 with AttributeError: 'GuiEvent' object has no attribute 'value' on every drag — GuiEvent exposes the changed control via .target, not .value directly.

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): Add position_frame='body'|'world' option to _compute_logmap_residuals() — choose body-frame or world-frame position error.
  • feat(urdf_exporter): New module figaroh.tools.urdf_exporter for applying calibrated joint parameters to URDF files with 12 joint-level categories.
  • feat(export_validation): New module figaroh.tools.export_validation with URDFComparison class — 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.
  • DynamicsBackend abstract interface with 9 abstract + 6 optional methods.
  • RobotIdentificationSystem high-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 missing mj_forward call).

Changed

  • breaking(export_validation): Rename trajectory_errors() → fk_consistency_check() and TrajectoryErrors → 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 module figaroh.identification.cad_constraints with convex CAD-informed constraints for inertial parameter identification.
  • CADConstraints dataclass — 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 constraint m_min ≤ m_j ≤ m_max for a link.
  • add_com_bounds(cad, joint, *, axis, h_min, h_max) — add first-moment (linear) box constraint h_min ≤ h_k ≤ h_max for axis k ∈ {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) — build CADConstraints from a YAML config sub-dict; returns None when empty (safe default-off).
  • apply_cad_constraints_to_problem(problem, theta, params_r, cad) — inject CAD constraints into a picos Problem for the full-robot SDP.
  • feat(physical_consistency): project_p10_lmi gains optional mass_bounds and com_bounds kwargs — per-link box constraints on the SDP.
  • feat(physical_consistency): project_robot_p10_lmi gains optional cad_constraints kwarg — passes per-link bounds to project_p10_lmi.
  • feat(reconstruction): _reconstruct_sdp and reconstruct_full_parameters gain optional cad_constraints kwarg — injects CAD constraints after the per-joint LMI loop in the full-robot SDP.
  • feat(base_identification): Both _apply_physical_consistency_if_enabled and _apply_reconstruction_if_enabled hooks parse cad_constraints from config and pass the result to the underlying solvers.
  • Example YAML comment block for cad_constraints in figaroh-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 result
  • feat(reconstruction): ReconstructionResult.base_residual_norm — L2 norm of the base constraint residual M θ − φ_base (scalar, always set)
  • feat(reconstruction): ReconstructionResult.objective — SDP objective value for Option B (None for nullspace)
  • feat(reconstruction): BaseResult frozen dataclass — structured container for (M, phi_base, params_r) as input to reconstruct_full_parameters()
  • feat(reconstruction): reconstruct_full_parameters() — unified entry point with method="nullspace" | "sdp" | "auto" dispatch
  • feat(reconstruction): Option B SDP (method="sdp") — minimises ‖diag(w)(θ−θ₀)‖² subject to M θ = φ_base and per-joint pseudo-inertia LMI P_j ≽ 0 via 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 and method="sdp" was requested explicitly
  • feat(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 in BaseIdentification._store_results() — called after physical consistency; stores result under self.result["reconstruction"]
  • feat(identification): _calculate_base_parameters() now uses QRDecomposer directly to expose self._M_matrix / self._params_r_for_recon for downstream reconstruction; result dict includes "M" and "params_r" keys
  • feat(config): reconstruction block parsed from both legacy YAML (get_param_from_yaml) and unified config (unified_to_legacy_identif_config)
  • feat(identification/__init__): BaseResult and reconstruct_full_parameters added to public exports and __all__
  • tests: 13 new unit tests in tests/unit/test_reconstruction.py covering 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 in run_reconstruction now correctly unpacks project_robot_p10_lmi() as (projected_p10_dict, robot_report); removes AttributeError: tuple has no attribute 'p10_by_joint'
  • fix(identification): "projected parameters" key (space) renamed to "projected_parameters" (underscore) in _apply_physical_consistency_if_enabled result dict for consistency with "raw_parameters" and Python conventions
  • fix(tools/robotcollisions): print_collision_pairs() uses print() instead of logger.info() so output is visible in interactive use and captured by tests
  • fix(tools/solver): LinearSolver._print_solution_info() uses print() instead of logger.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 for check_p10_feasibility() matching roadmap spec naming
  • feat(physical-consistency): project_link() — public alias for project_p10_lmi() matching roadmap spec naming
  • feat(physical-consistency): ProjectionReport.runtime — per-link solve time (seconds) recorded via time.perf_counter() around problem.solve()
  • feat(physical-consistency): weights keyword argument to project_robot_p10_lmi() for passing a 10-element weight vector to all per-link projection calls
  • feat(config): weights.mode: "auto" | "manual" parsed from the physical_consistency YAML block in _apply_physical_consistency_if_enabled
  • feat(config): weights.manual.{m, h, Sigma} per-group manual weights built into a 10-element array and forwarded to project_robot_p10_lmi
  • feat(identification): physical consistency result dict now stores both raw_parameters (pre-projection) and projected_parameters (post-projection) as separate keys, preserving the original identified values for comparison
  • tests: 28 unit tests in tests/unit/test_physical_consistency.py covering 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 in project_p10_lmi no longer uses the picos 2.x-incompatible pc.vstack, pc.multiply, or pc.sum_squares functions; replaced with element-wise objective using pc.SquaredNorm and explicit sigma-entry loop
  • fix(physical-consistency): solver keyword verbose= replaced with verbosity= (picos 2.x API); eliminates DeprecationWarning on every call
  • fix(physical-consistency): feasibility check after projection uses a relaxed psd_eig_tol=-1e-8 tolerance 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_reconstruction and run_option_a_reconstruction entry points (identification/reconstruction.py)
  • feat(qrdecomposition): get_M / get_M_labels methods for retrieving the stored base mapping matrix after decomposition
  • feat(qrdecomposition): QRResult dataclass for structured, full-precision decomposition output (rank, base_indices, W_b, beta, M, phi_b, diag_R, cond_R1, method, …)
  • feat(qrdecomposition): relative_tolerance constructor 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 decomposition
  • feat(qrdecomposition): decompose() unified entry point returning a QRResult; delegates to pivoting or double path based on method argument
  • feat(identification): enhanced parameter management with standard and custom parameter support in BaseIdentification

Fixed

  • fix(qrdecomposition): _find_rank now correctly returns 0 for zero/empty matrices (previously returned row count, causing phantom base parameters)
  • fix(reconstruction): add missing run_option_a_reconstruction alias that blocked package-level import

Changed

  • refactor(qrdecomposition): replace non-pivoted numpy.linalg.qr with scipy.linalg.qr(..., pivoting=True) in _identify_base_parameters — permutation-stable, deterministic basis selection
  • refactor(qrdecomposition): remove premature np.around from beta in all code paths; rounding deferred to display-only _build_parameter_expressions
  • refactor(qrdecomposition): remove fragile assert np.allclose(W_base, W_b)
  • refactor(qrdecomposition): enhanced docstrings and nominal-parameter handling
  • refactor(identification): patch filter configuration management in BaseIdentification
  • refactor(calibration): update deprecated Frame.parent → Frame.parentJoint for Pinocchio 3.x compatibility
  • refactor(logging): replace print statements with logging calls across calibration and identification modules
  • refactor(config): rename load_from_yaml → load_param for clarity

Tests

  • test(qrdecomposition): TestNumericalImprovements suite — 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 tau parameter 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 cyipopt from 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 cyipopt or 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.txt and setup.py files
  • Consolidated all dependencies in pyproject.toml
  • Updated to use PyPI versions of robotics libraries (pin for 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.txt and setup.py in favor of modern pyproject.toml
  • Unnecessary Dependencies: Cleaned up unused dependencies for leaner installation

Fixed

  • Package Name: Corrected dependency references (e.g., proper use of pin for 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 UnifiedConfigParser with 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 support
  • BaseIdentification: 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 construction
  • RegressorConfig: 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 function
  • unified_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+