Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ mosek_output.tmp
bin/act
docs/build/doctest
docs/build/
.mypy_cache
24 changes: 16 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased] -
## [Unreleased]

### Added

(0.0.3)
### Changed

### Fixed

## [0.0.4] - 2026-04-08

### Changed
- Fix the ground truth value for Poly4 type B
- Creating fixed examples is now handled by staticmethod create_example()
- Added this for: Poly4Lifter, Poly6Lifter, RotationLifter, RangeOnlySqLifter, RangeOnlyNsqLifter
- Pending: other lifters: create this functionality
- Created two plotting types: plot_cost and plot_setup
- Added this for: Poly4Lifter, Poly6Lifter, RotationLifter, RangeOnlySqLifter, RangeOnlyNsqLifter
- Pending: make sure all others also conform to this new structure

## [0.0.3] - 2026-04-03

(0.0.3a)=
### Added
- Add example cases to RotationLifter and corresponding tests.
- RotationLifter: support to plot 2d frames, improved documentation for rotation conventions
Expand All @@ -23,32 +36,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- StateLifter: started support for rank-d instead of rank-1 formulations
- Added more documentation and type hints

(0.0.3c)=
### Changed
- RangeOnlyLifter: default sampling for landmarks in RO is now the one filling space
- RangeOnlyLifter: theta is sampled at least MIN_DIST away from landmarks
- Always returning constraints matrices along with right-hand-side values

(0.0.3f)=
### Fixed
- Link fixes in documentation

## [0.0.2] - 2025-06-04

(0.0.2a)=
### Added

- RangeOnlyLifter base class for 2D/3D range-only localization (base_lifters/range_only_lifter.py)
- RangeOnlyNsqLifter: specialized lifter for range-only localization without squared distances (examples/ro_nsq_lifter.py)
- RangeOnlySqLifter: specialized lifter for squared-distance-based range-only localization
- GitHub Pages deployment step to documentation.yml using peaceiris/actions-gh-pages for automatic documentation publishing

(0.0.2r)=
### Removed

- docs/build/ files

(0.0.2f)=
### Fixed

- Corrected return type annotations in get_grad and get_hess methods in state_lifter.py from float to np.ndarray
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Please try to the best of your abilities to:

## Adding a new lifter class

You can start with the [ExampleLifter](../popcor/examples/ExampleLifter.py) skeleton, and feel free to add more functionalities depending on the nature of the problem. You can also consider adding a new base class similar to [RobustPoseLifter](../popcor/base_lifters/RobustPoseLifter.py) or [StereoLifter](../popcor/base_lifters/StereoLifter.py) if you want to create multiple new lifters that all share similar functionalities.
You can start with the `example_lifter.py` skeleton in `popcor/examples/`, and feel free to add more functionalities depending on the nature of the problem. You can also consider adding a new base class similar to `robust_pose_lifter.py` or `stereo_lifter.py` (both in `popcor/base_lifters/`) if you want to create multiple new lifters that all share similar functionalities.

## Adding new functionalities

Expand Down
Binary file added docs/source/_static/ro_nsq_cost_A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_nsq_cost_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_nsq_cost_C.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_nsq_setup_A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_nsq_setup_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_nsq_setup_C.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_cost_A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_cost_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_cost_C.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_setup_A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_setup_B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/_static/ro_sq_setup_C.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@
import os
import sys

import popcor

# The module you're documenting (assumes you've added the project root dir to sys.path)
sys.path.insert(0, os.path.abspath("../.."))

try:
import popcor
except (ImportError, ModuleNotFoundError):
popcor = None

# -- Project information -----------------------------------------------------

project = "POPCOR"
Expand Down
88 changes: 88 additions & 0 deletions docs/source/examples/standard.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,94 @@ Range-Only Localization
:undoc-members:
:show-inheritance:

RangeOnlySqLifter Example Plots
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The below plots are generated by calling the following lines of code.
You can find the full code in the ``__main__`` section of the corresponding module.

.. code-block:: python

lifter = RangeOnlySqLifter.create_example(example_type="A") # choose A, B or C
lifter.plot_cost()


.. figure:: /_static/ro_sq_cost_A.png
:alt: RangeOnlySqLifter cost heatmap type A
:align: center
:figclass: align-center

RangeOnlySqLifter cost heatmap (Type A)

.. figure:: /_static/ro_sq_cost_B.png
:alt: RangeOnlySqLifter cost heatmap type B
:align: center
:figclass: align-center

RangeOnlySqLifter cost heatmap (Type B)

.. figure:: /_static/ro_sq_cost_C.png
:alt: RangeOnlySqLifter cost heatmap type C
:align: center
:figclass: align-center

RangeOnlySqLifter cost heatmap (Type C)


RangeOnlyNsqLifter Example Plots
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


The below plots are generated by calling the following lines of code.
You can find the full code in the ``__main__`` section of the corresponding module.

.. code-block:: python

lifter = RangeOnlyNsqLifter.create_example(example_type="A") # choose A, B or C
lifter.plot_cost()

.. figure:: /_static/ro_nsq_cost_A.png
:alt: RangeOnlyNsqLifter cost heatmap type A
:align: center
:figclass: align-center

RangeOnlyNsqLifter cost heatmap (Type A)

.. figure:: /_static/ro_nsq_setup_A.png
:alt: RangeOnlyNsqLifter setup plot type A
:align: center
:figclass: align-center

RangeOnlyNsqLifter setup (Type A)

.. figure:: /_static/ro_nsq_cost_B.png
:alt: RangeOnlyNsqLifter cost heatmap type B
:align: center
:figclass: align-center

RangeOnlyNsqLifter cost heatmap (Type B)

.. figure:: /_static/ro_nsq_setup_B.png
:alt: RangeOnlyNsqLifter setup plot type B
:align: center
:figclass: align-center

RangeOnlyNsqLifter setup (Type B)

.. figure:: /_static/ro_nsq_cost_C.png
:alt: RangeOnlyNsqLifter cost heatmap type C
:align: center
:figclass: align-center

RangeOnlyNsqLifter cost heatmap (Type C)

.. figure:: /_static/ro_nsq_setup_C.png
:alt: RangeOnlyNsqLifter setup plot type C
:align: center
:figclass: align-center

RangeOnlyNsqLifter setup (Type C)


Stereo-Camera Localization
--------------------------
Expand Down
8 changes: 8 additions & 0 deletions docs/source/examples/toy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Toy Examples
Univariate Polynomials
----------------------

The below plots are generated by calling the following lines of code.
You can find the full code in the ``__main__`` section of the corresponding module.

.. code-block:: python

lifter = Poly4Lifter.create_example(example_type="A") # choose A or B
lifter.plot_cost()

.. autoclass:: popcor.examples.Poly4Lifter
:undoc-members:
:show-inheritance:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ More information on how to use AutoTemplate can be found :ref:`here <AutoTemplat
.. literalinclude:: ../../tests/test_quickstart.py
:language: python
:lines: 106-135
:dedent: 4
:dedent: 0
Comment on lines 139 to +142
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AutoTemplate literalinclude points at code inside a test function (indented). Using ":dedent: 0" will include the extra indentation in the rendered docs and makes it inconsistent with the other snippets above (which use dedent 4). Consider switching this back to ":dedent: 4" for consistent formatting.

Copilot uses AI. Check for mistakes.


References
Expand Down
8 changes: 3 additions & 5 deletions docs/source/whatsnew.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
What's new
What's New
==========

Below is a rendering of the CHANGELOG.md file.

.. include:: ../../CHANGELOG.md
:parser: myst_parser.sphinx_
.. literalinclude:: ../../CHANGELOG.md
:language: markdown
3 changes: 2 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ dependencies:
- spatialmath-python=1.1.11
- suitesparse # used by sparseqr
- pip:
- -r doc/requirements.txt
- sparseqr>=1.6.0
- -r docs/requirements.txt
- -e .
1 change: 1 addition & 0 deletions environment_small.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ dependencies:

- suitesparse # used by sparseqr
- pip:
- sparseqr>=1.6.0
- -e .
2 changes: 1 addition & 1 deletion popcor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .auto_template import AutoTemplate
from .auto_tight import AutoTight

__version__ = "0.0.3"
__version__ = "0.0.4"
6 changes: 3 additions & 3 deletions popcor/auto_tight.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def generate_matrices_simple(
elif normalize and sparse:
Ai /= splinalg.norm(Ai, ord="fro")
if sparse:
assert isinstance(Ai, (sp.csr_matrix, sp.csc_matrix))
assert isinstance(Ai, (sp.csr_array, sp.csc_array))
Ai.eliminate_zeros()
else:
assert isinstance(Ai, np.ndarray)
Expand Down Expand Up @@ -338,7 +338,7 @@ def generate_matrices(
ai = lifter.get_reduced_a(bi=basis[i], var_subset=var_dict, sparse=True)
basis_reduced.append(ai)
basis_reduced = sp.vstack(basis_reduced)
assert isinstance(basis_reduced, sp.csr_matrix)
assert isinstance(basis_reduced, sp.csr_array)
if AutoTight.REDUCE_DEPENDENT:
bad_idx = find_dependent_columns(basis_reduced.T, tolerance=1e-6)
else:
Expand All @@ -355,7 +355,7 @@ def generate_matrices(
elif normalize and sparse:
Ai /= splinalg.norm(Ai, ord="fro")
if sparse:
assert isinstance(Ai, (sp.csr_matrix, sp.csc_matrix))
assert isinstance(Ai, (sp.csr_array, sp.csc_array))
Ai.eliminate_zeros()
else:
assert isinstance(Ai, np.ndarray)
Expand Down
30 changes: 25 additions & 5 deletions popcor/base_lifters/poly_lifter.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,34 @@ def local_solver(self, t0, y=None, *args, **kwargs):
info = {"success": sol.success, "cost": sol.fun}
return sol.x, info, sol.fun

def plot(self, thetas, label=None):
def plot(self, thetas=None, label=None, estimates=None):
from popcor.utils.plotting_tools_poly import coordinate_system

fig, ax = coordinate_system()
ys = [self.get_cost(t) for t in thetas]
ax.plot(thetas, ys, label=label)
ymin = min(-max(ys) / 3, min(ys))
ax.set_ylim(ymin, max(ys))

# Handle the case where estimates is provided but thetas is not
if thetas is None and estimates is None:
raise ValueError("Either thetas or estimates must be provided")

if thetas is not None:
ys = [self.get_cost(t) for t in thetas]
ax.plot(thetas, ys, label=label)
ymin = min(-max(ys) / 3, min(ys))
ax.set_ylim(ymin, max(ys))

# Plot estimates if provided
if estimates is not None:
for est_label, theta_est in estimates.items():
cost_est = self.get_cost(theta_est)
ax.scatter(
[theta_est],
[cost_est],
label=est_label,
s=100,
marker="x",
linewidth=2,
)

return fig, ax

def __repr__(self):
Expand Down
Loading
Loading