diff --git a/doc/manual/index.rst b/doc/manual/index.rst index eef4f56f0..642b81072 100644 --- a/doc/manual/index.rst +++ b/doc/manual/index.rst @@ -214,11 +214,15 @@ User manual * The class SetMembershipFunction * Extension to custom expressions -* :ref:`sec-domains-tubes` - * :ref:`sec-domains-tubes-tdomain` - * :ref:`sec-domains-tubes-slicedtube` - * The Trajectory classes - * Increasing performances using views +* :ref:`sec-tubes-trajectories` + * :ref:`sec-trajectories-sampledtraj` + * :ref:`sec-trajectories-analytictraj` + * :ref:`sec-trajectories-operations` + * :ref:`sec-trajectories-io` + * :ref:`sec-tubes-tdomain` + * :ref:`sec-tubes-slice` + * :ref:`sec-tubes-slicedtube` + * :ref:`sec-tubes-examples` * Contractors * What are contractors? @@ -354,7 +358,7 @@ User manual * Related papers * Contributors * How to cite Codac - :ref:`sec-ref-codac-logos` + * :ref:`sec-ref-codac-logos` How-to guides diff --git a/doc/manual/manual/cn/index.rst b/doc/manual/manual/cn/index.rst new file mode 100644 index 000000000..c265a319c --- /dev/null +++ b/doc/manual/manual/cn/index.rst @@ -0,0 +1,7 @@ +Contractor Networks +=================== + +.. toctree:: + + What is a CN? + The ContractorNetwork class \ No newline at end of file diff --git a/doc/manual/manual/contractors/basic/index.rst b/doc/manual/manual/contractors/basic/index.rst new file mode 100644 index 000000000..a98cb5aed --- /dev/null +++ b/doc/manual/manual/contractors/basic/index.rst @@ -0,0 +1,9 @@ +Basic contractors +================= + +.. toctree:: + + CtcIdentity + CtcEmpty + CtcLazy + CtcFixpoint \ No newline at end of file diff --git a/doc/manual/manual/contractors/index.rst b/doc/manual/manual/contractors/index.rst index 8e6eea3c8..67dab8f9e 100644 --- a/doc/manual/manual/contractors/index.rst +++ b/doc/manual/manual/contractors/index.rst @@ -1,4 +1,5 @@ .. _sec-ctc: + Contractors, separators ======================= diff --git a/doc/manual/manual/contractors/linear/index.rst b/doc/manual/manual/contractors/linear/index.rst new file mode 100644 index 000000000..b0106ece9 --- /dev/null +++ b/doc/manual/manual/contractors/linear/index.rst @@ -0,0 +1,8 @@ +Linear contractors +================== + +.. toctree:: + + CtcGaussElim + CtcGaussSeidel + CtcLinearPrecond \ No newline at end of file diff --git a/doc/manual/manual/contractors/set/ctcunion.rst b/doc/manual/manual/contractors/set/ctcunion.rst new file mode 100644 index 000000000..7c69d0970 --- /dev/null +++ b/doc/manual/manual/contractors/set/ctcunion.rst @@ -0,0 +1,188 @@ +.. _sec-ctc-set-ctcunion: +.. _sec-sep-set-sepunion: + +Union: CtcUnion and SepUnion +============================ + + Main author: `Simon Rohou `_ + + +``CtcUnion`` and ``SepUnion`` implement the logical union/disjunction counterpart of +``CtcInter`` and ``SepInter``: + +.. list-table:: + :header-rows: 1 + :widths: 20 35 45 + + * - Class + - Combines + - Meaning + * - ``CtcUnion`` + - contractors + - disjunction of constraints, obtained by joining the contracted branches + * - ``SepUnion`` + - separators + - separator associated with the union of the represented sets + +Both classes can be constructed explicitly or with the ``|`` operator. + + +CtcUnion: disjunction of contractors +------------------------------------ + +For box contractors, ``CtcUnion`` applies every stored contractor independently to the +same input box and returns a box enclosing the union of the resulting branches: + +.. math:: + + \mathcal{C}_{\mathrm{union}}([\mathbf{x}]) + = + \operatorname{hull}\!\left( + \bigcup_{i=1}^{n}\mathcal{C}_i([\mathbf{x}]) + \right), + +where :math:`\operatorname{hull}` denotes the smallest box containing the union. + +This evaluation differs from ``CtcInter``: the branches are **not** applied +sequentially. Each contractor receives the original input domain. + +.. note:: + + The exact union of several boxes is generally not a box. ``CtcUnion`` therefore + returns a box enclosure of this union. This enclosure may contain points that do not + belong to any individual branch. + +The usual construction uses ``|`` or the ``CtcUnion`` constructor. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: py + :start-after: [union-ctc-1-beg] + :end-before: [union-ctc-1-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [union-ctc-1-beg] + :end-before: [union-ctc-1-end] + :dedent: 2 + +Applying it to a box gives the hull of the contracted branches: + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: py + :start-after: [union-ctc-2-beg] + :end-before: [union-ctc-2-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [union-ctc-2-beg] + :end-before: [union-ctc-2-end] + :dedent: 2 + +For the two boxes used here, the exact union is not itself a box. Its interval hull is + +.. math:: + + [-12,10]\times[-2,4]. + + +SepUnion: union of represented sets +----------------------------------- + +``SepUnion`` is the separator counterpart. If separators +:math:`\mathcal{S}_1,\ldots,\mathcal{S}_n` are associated with sets +:math:`\mathbb{S}_1,\ldots,\mathbb{S}_n`, then ``SepUnion`` is associated with + +.. math:: + + \mathbb{S}_{\mathrm{union}} + = + \mathbb{S}_1 \cup \cdots \cup \mathbb{S}_n. + +Construction follows the same syntax as for ``CtcUnion``: + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: py + :start-after: [union-sep-1-beg] + :end-before: [union-sep-1-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [union-sep-1-beg] + :end-before: [union-sep-1-end] + :dedent: 2 + + +Building a union incrementally +------------------------------ + +Both classes support ``|=`` and ``nb()``. + +For ``CtcUnion``, a union may be created from the dimension only. Before a branch is +added, it represents the empty disjunction: contracting a box with it returns the empty +domain. Adding contractors with ``|=`` then builds the disjunction incrementally. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: py + :start-after: [union-ctc-3-beg] + :end-before: [union-ctc-3-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [union-ctc-3-beg] + :end-before: [union-ctc-3-end] + :dedent: 2 + +``SepUnion`` currently has no constructor taking only a dimension, so incremental +construction starts from an existing separator: + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: py + :start-after: [union-sep-2-beg] + :end-before: [union-sep-2-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [union-sep-2-beg] + :end-before: [union-sep-2-end] + :dedent: 2 + + +.. admonition:: Technical documentation + + See the `C++ API documentation of CtcUnion <../../api/html/classcodac2_1_1_ctc_union.html>`_ + and the `C++ API documentation of SepUnion <../../api/html/classcodac2_1_1_sep_union.html>`_. diff --git a/doc/manual/manual/contractors/temporal/index.rst b/doc/manual/manual/contractors/temporal/index.rst new file mode 100644 index 000000000..35e98efe5 --- /dev/null +++ b/doc/manual/manual/contractors/temporal/index.rst @@ -0,0 +1,14 @@ +Temporal contractors +==================== + +.. toctree:: + + Using static contractors on tubes + CtcDeriv + CtcEval + CtcDelay + CtcLinobs + CtcLohner + CtcPicard + CtcChain + CtcDiffInclusion \ No newline at end of file diff --git a/doc/manual/manual/extensions/ibex/index.rst b/doc/manual/manual/extensions/ibex/index.rst new file mode 100644 index 000000000..ac2ca5561 --- /dev/null +++ b/doc/manual/manual/extensions/ibex/index.rst @@ -0,0 +1,7 @@ +Interface with the IBEX library +=============================== + +.. toctree:: + + Installing the codac-ibex extension + How to use \ No newline at end of file diff --git a/doc/manual/manual/extensions/unsupported/index.rst b/doc/manual/manual/extensions/unsupported/index.rst new file mode 100644 index 000000000..617cae776 --- /dev/null +++ b/doc/manual/manual/extensions/unsupported/index.rst @@ -0,0 +1,6 @@ +Unsupported features of Codac +============================= + +.. toctree:: + + Installing the codac-unsupported extension \ No newline at end of file diff --git a/doc/manual/manual/functions/analytic/analytic_operators.rst b/doc/manual/manual/functions/analytic/analytic_operators.rst index 2539a344f..06333a20b 100644 --- a/doc/manual/manual/functions/analytic/analytic_operators.rst +++ b/doc/manual/manual/functions/analytic/analytic_operators.rst @@ -350,6 +350,8 @@ Temporal operations for involving trajectories or tubes in analytic expressions ); +For a detailed example using ``SampledTraj.as_function()`` and information on pointwise ``AnalyticFunction.traj_eval(...)``, see :ref:`sec-trajectories-operations`. + Direct use of operators ^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/manual/manual/functions/analytic/index.rst b/doc/manual/manual/functions/analytic/index.rst index 82496722e..5d93afafb 100644 --- a/doc/manual/manual/functions/analytic/index.rst +++ b/doc/manual/manual/functions/analytic/index.rst @@ -5,7 +5,5 @@ Analytic inclusion functions .. toctree:: - analytic_functions.rst - analytic_operators.rst - Extension to custom expressions - Temporal functions \ No newline at end of file + analytic_functions.rst + analytic_operators.rst \ No newline at end of file diff --git a/doc/manual/manual/functions/index.rst b/doc/manual/manual/functions/index.rst index b70ce037d..35d7a8bd0 100644 --- a/doc/manual/manual/functions/index.rst +++ b/doc/manual/manual/functions/index.rst @@ -4,9 +4,9 @@ Inclusion functions =================== .. toctree:: + :maxdepth: 1 - analytic/analytic_functions.rst - analytic/analytic_operators.rst - parallelepiped/parallelepiped_eval.rst - peibos/peibos.rst - .. Set-membership functions \ No newline at end of file + analytic/analytic_functions.rst + analytic/analytic_operators.rst + parallelepiped/parallelepiped_eval.rst + peibos/peibos.rst \ No newline at end of file diff --git a/doc/manual/manual/functions/setmembership/index.rst b/doc/manual/manual/functions/setmembership/index.rst new file mode 100644 index 000000000..ede97a732 --- /dev/null +++ b/doc/manual/manual/functions/setmembership/index.rst @@ -0,0 +1,7 @@ +Set-membership functions +======================== + +.. toctree:: + + The class SetMembershipFunction + Extension to custom expressions \ No newline at end of file diff --git a/doc/manual/manual/intervals/Interval_class.rst b/doc/manual/manual/intervals/Interval_class.rst index 39f49a7d7..11450b9c6 100644 --- a/doc/manual/manual/intervals/Interval_class.rst +++ b/doc/manual/manual/intervals/Interval_class.rst @@ -257,6 +257,8 @@ For instance, ``x < y`` returns: - ``BoolInterval.UNKNOWN`` otherwise. +.. _sec-intervals-class-un-bin-ary: + Unary and binary functions -------------------------- diff --git a/doc/manual/manual/intervals/Vector_class.rst b/doc/manual/manual/intervals/Vector_class.rst new file mode 100644 index 000000000..c7c937537 --- /dev/null +++ b/doc/manual/manual/intervals/Vector_class.rst @@ -0,0 +1,217 @@ +.. _sec-vector-class: + +Vectors, rows and Matrices +========================== + +This page describes how to construct, use, and manipulate ``Vector``, ``Row`` and ``Matrix`` objects. +Vectors and rows are treated as one-dimensional matrices (either rows or columns), and most functionality is shared with general matrices. + +Codac uses the `Eigen library `_ for its matricial structures and calculations, with `custom methods added inside the Eigen classes `_. In the same way, the interval counterpart objects (``IntervalVector``, ``IntervalRow`` and ``IntervalMatrix``) adopt the structure of Eigen (same class name or operations) with an interval arithmetic and reliable outputs. + +The following sections illustrate construction, element access, initialization, resizing, substructure manipulation, and various properties of vectors and matrices. + + +Construction +------------ + +Vectors and matrices can be constructed using initializer lists, sizes, or raw arrays. Vectors are typically used when either the number of rows or columns is fixed to 1. + +.. tabs:: + + .. code-tab:: cpp + + // Vector from initializer list + Vector v = {1.0, 2.0, 3.0}; + + // Vector from std::vector + Vector v2(std::vector{4.0, 5.0, 6.0}); + + // Vector of fixed size, zero-initialized (if scalar type is not interval) + Vector v3(3); + + // Vector from array + double data[] = {7.0, 8.0, 9.0}; + Vector v4(3, data); + + .. code-tab:: cpp + + // Matrix with given number of rows and columns + Vector M(2, 3); // 2 rows, 3 columns + + // Matrix from array + double values[] = {1, 2, 3, 4, 5, 6}; + Vector M2(2, 3, values); // fills elements row-by-row + + // Matrix with uninitialized (or zero) values + Vector M3(2, 3); + +Element Access +-------------- + +Vector and matrix elements can be accessed via parentheses `()` or brackets `[]` (for vectors). You can both read and write elements. + +.. tabs:: + + .. code-tab:: cpp + + // Vector access + double x = v[0]; + v(1) = 42.0; + + .. code-tab:: cpp + + // Matrix access + double y = M(1, 2); // row 1, column 2 + M(0, 0) = 3.14; + +Initialization +-------------- + +You can set all entries of a matrix or vector to a given value using `init()`: + +.. tabs:: + + .. code-tab:: cpp + + v.init(0.0); // Set all elements of vector to 0 + + .. code-tab:: cpp + + M.init(1.0); // Set all matrix elements to 1 + +Equality Comparison +------------------- + +Check whether two objects are equal in shape and contents using `==`. + +.. tabs:: + + .. code-tab:: cpp + + if (v == v2) + std::cout << "Vectors are equal.\n"; + + if (M == M2) + std::cout << "Matrices match.\n"; + +Factory Constructors +-------------------- + +Use these static constructors to create matrices or vectors with common patterns. + +.. tabs:: + + .. code-tab:: cpp + + Vector v1 = Vector::zero(4); // Vector with 4 zeros + Vector v2 = Vector::ones(4); // Vector with all 1s + Vector v3 = Vector::constant(4, 3.14); // Vector filled with 3.14 + Vector v4 = Vector::random(4); // Random values + + .. code-tab:: cpp + + Vector M1 = Vector::zero(2, 3); // 2x3 matrix with zeros + Vector M2 = Vector::ones(2, 3); // matrix with ones + Vector M3 = Vector::constant(2, 3, 2.5); // constant values + Vector M4 = Vector::eye(3, 3); // 3x3 identity matrix + Vector M5 = Vector::random(2, 3); // matrix with random elements + +Resize (with Data Preservation) +------------------------------- + +Unlike Eigen’s default `resize()`, these methods preserve overlapping values during resizing. + +.. tabs:: + + .. code-tab:: cpp + + v.resize_save_values(6); // Resize vector from 3 to 6 elements + + .. code-tab:: cpp + + M.resize_save_values(4, 5); // Resize 2x3 matrix to 4x5 + +Substructures and Assignment +---------------------------- + +You can extract subvectors and insert values from one vector to another. + +.. tabs:: + + .. code-tab:: cpp + + Vector sub = v.subvector(1, 3); // Extract elements v[1] to v[3] + v.put(2, sub); // Insert sub at index 2 + +Properties and Structural Checks +-------------------------------- + +.. tabs:: + + .. code-tab:: cpp + + bool square = M.is_squared(); // True if M is square (rows == cols) + bool contains_nan = M.is_nan(); // Any NaN values? + + double norm2 = v.squared_norm(); // ||v||^2 + double min_val = v.min_coeff(); // Minimum entry + double max_val = v.max_coeff(); // Maximum entry + + Index idx_min = v.min_coeff_index(); // Index of min value + Index idx_max = v.max_coeff_index(); // Index of max value + + .. code-tab:: cpp + + auto D = v.diag_matrix(); // Convert vector into diagonal matrix + +Element-wise Operations +----------------------- + +Apply rounding operations to each element. + +.. tabs:: + + .. code-tab:: cpp + + auto vf = floor(v); // floor of each value + auto vc = ceil(v); // ceil of each value + auto vr = round(v); // round to nearest + + .. code-tab:: cpp + + auto mf = floor(M); // floor of each element + auto mc = ceil(M); + auto mr = round(M); + +.. note:: + + These rounding operations are disabled for matrices with interval elements. + +Examples +-------- + +Here’s a short example illustrating usage of several features together: + +.. tabs:: + + .. code-tab:: cpp + + Vector a = Vector::random(5); + Vector b = Vector::ones(5); + + a.init(3.14); + double max = a.max_coeff(); + + Vector diag = a.diag_matrix(); + + Vector slice = a.subvector(1, 3); + a.put(0, slice); + + if (a == b) + std::cout << "Same values!" << std::endl; + +See Also +-------- + +- :ref:`Interval class documentation ` +- :ref:`Matrix operations reference ` \ No newline at end of file diff --git a/doc/manual/manual/intervals/index.rst b/doc/manual/manual/intervals/index.rst index 368899992..bd3078c8e 100644 --- a/doc/manual/manual/intervals/index.rst +++ b/doc/manual/manual/intervals/index.rst @@ -15,7 +15,6 @@ Codac provides data structures for handling basic interval sets. These structure :hidden: Interval_class.rst - .. Vector_class.rst IntervalVector_class.rst BoolInterval_class.rst diff --git a/doc/manual/manual/linear/eigen.rst b/doc/manual/manual/linear/eigen.rst new file mode 100644 index 000000000..41840a5e0 --- /dev/null +++ b/doc/manual/manual/linear/eigen.rst @@ -0,0 +1,250 @@ +C++: efficient matrix operations using Eigen +============================================ + +Eigen is a C++ template library for linear algebra that provides powerful tools for matrix and vector operations. One of Eigen's key features is its use of **expression templates**, which allow efficient and optimized computations. This page explains how to leverage Eigen for high-performance matrix operations. + +What Are Expression Templates? +------------------------------ +Expression templates are a metaprogramming technique used in Eigen to delay the evaluation of mathematical expressions. Instead of creating intermediate objects for every operation, Eigen builds an abstract representation of the computation and evaluates it only when necessary. This approach reduces memory usage and improves performance. + +For example: + +.. code-block:: cpp + + #include + #include + + int main() { + Eigen::MatrixXd A = Eigen::MatrixXd::Random(3, 3); + Eigen::MatrixXd B = Eigen::MatrixXd::Random(3, 3); + Eigen::MatrixXd C = Eigen::MatrixXd::Random(3, 3); + + // The expression (A + B) * C is not evaluated immediately. + Eigen::MatrixXd D = (A + B) * C; + + std::cout << "Result: \n" << D << std::endl; + return 0; + } + +In this example, the computation `(A + B)` is not immediately performed. Instead, Eigen builds a representation of the entire expression `(A + B) * C` and optimizes it before execution. + +Key Advantages +-------------- +- **No Temporary Objects:** Expression templates avoid creating temporary matrices for intermediate results. +- **Optimized Operations:** Eigen combines multiple operations into a single, efficient computation. +- **Readable Code:** High-level syntax simplifies complex mathematical expressions. + +Efficient Use of Eigen +---------------------- +To maximize efficiency with Eigen, follow these best practices: + +1. **Avoid Explicit Loops:** Use Eigen's built-in functions and operators to manipulate matrices and vectors. + + .. code-block:: cpp + + // Inefficient: Explicit loop + for (int i = 0; i < A.rows(); ++i) { + for (int j = 0; j < A.cols(); ++j) { + A(i, j) += B(i, j); + } + } + + // Efficient: Vectorized addition + A += B; + +2. **Use Fixed-Size Matrices When Possible:** Eigen is optimized for small, fixed-size matrices. + + .. code-block:: cpp + + Eigen::Matrix3d A; // Fixed size 3x3 matrix + Eigen::Vector3d v; // Fixed size 3x1 vector + +3. **Leverage Lazy Evaluation:** Combine operations to benefit from expression templates. + + .. code-block:: cpp + + Eigen::MatrixXd A, B, C, D; + D = A + B + C; // Combined into a single operation + +4. **Enable Vectorization:** Eigen can use SIMD instructions for certain operations. To ensure vectorization is enabled, define the following macro: + + .. code-block:: cpp + + #define EIGEN_USE_BLAS + +Common Pitfalls +--------------- +1. **Unaligned Memory:** Avoid operations on matrices with unaligned memory, as they may lead to performance degradation. +2. **Unnecessary Copies:** Avoid explicit copies of matrices unless required. + + .. code-block:: cpp + + Eigen::MatrixXd A = B + C; // Efficient + Eigen::MatrixXd D = A; // Inefficient if unnecessary + +Further Reading +--------------- +- `Eigen Documentation `_ +- `Expression Templates Explained `_ + +By understanding and utilizing Eigen's expression templates, you can write efficient, high-performance matrix computations that are both elegant and powerful. + + +Limitations in Python +---------------------- + +Although expression templates provide significant performance benefits in C++, they cannot be directly applied in Python due to the differences in how the two languages handle operations and memory. + +In Python, operations on matrices are typically not evaluated lazily, and Python's dynamic nature does not support the same kind of compile-time optimizations that C++ offers. As a result, when performing matrix operations in Python, intermediate objects are often created, leading to potential performance bottlenecks, especially for large-scale computations. + +Libraries like NumPy in Python do use optimized C extensions behind the scenes, but they rely on a different approach (such as broadcasting and memory views) to achieve performance. While NumPy can handle many operations efficiently, it does not implement expression templates in the same way as Eigen in C++. + +Therefore, while the concept of expression templates is powerful in C++, it is not directly transferable to Python without significant adjustments to the language's execution model. + + + + + +Using Eigen in Codac for Interval Matrices +------------------------------------------- + +In the Codac library, Eigen's powerful template-based architecture is leveraged to handle matrices of intervals. By utilizing the `Interval` class from Codac, we can extend the capabilities of Eigen to represent matrices that store intervals, rather than just fixed values. This allows Codac to efficiently perform computations on uncertain or bounded data, which is common in constraint programming and optimization problems. + +An `Interval` represents a range of values, typically defined by a lower and upper bound. When creating matrices of `Interval` objects, we can perform interval arithmetic directly, benefiting from Eigen’s expression templates for efficient evaluation. + +For example, you can create a matrix of intervals as follows: + +.. code-block:: cpp + + #include + #include + #include + + int main() { + // Create two 3x3 matrices of intervals + Codac::Interval a(1, 3), b(2, 5), c(4, 6); + Eigen::Matrix, 3, 3> M1; + Eigen::Matrix, 3, 3> M2; + + M1 << a, b, c, + b, c, a, + c, a, b; + + M2 << c, a, b, + a, b, c, + b, c, a; + + // Perform matrix addition with interval values + Eigen::Matrix, 3, 3> M3 = M1 + M2; + + std::cout << "Matrix of Intervals M3: \n" << M3 << std::endl; + return 0; + } + +In this example, `M1` and `M2` are matrices of `Interval` objects, and we perform matrix addition as usual. The result, `M3`, is also a matrix of intervals, where each entry is the sum of the corresponding interval values from `M1` and `M2`. + +This approach allows Codac to work with uncertain or constrained data efficiently, benefiting from Eigen’s optimizations and providing robust handling of interval-based computations. + + + + +Eigen in Codac: Interval Matrices +---------------------------------- + +In the **Codac** library, Eigen is leveraged to efficiently handle **interval matrices** using its template-based approach. The `Interval` object in Codac represents a mathematical interval, and combining this with Eigen's matrix operations allows for the creation and manipulation of matrices that store intervals instead of exact values. This is particularly useful in fields such as constraint programming, where uncertainty or variability in data needs to be represented. + +The use of Eigen’s expression templates ensures that operations on these interval matrices remain efficient, avoiding unnecessary intermediate computations. + +### Example 1: Creating an Interval Matrix + +.. code-block:: cpp + + #include // Codac-specific headers + #include + #include + + int main() { + // Define an interval matrix + Eigen::Matrix M; + + // Initialize with some intervals + M(0, 0) = Interval(1.0, 2.0); // Interval from 1.0 to 2.0 + M(0, 1) = Interval(2.0, 3.0); // Interval from 2.0 to 3.0 + M(1, 0) = Interval(3.0, 4.0); // Interval from 3.0 to 4.0 + M(1, 1) = Interval(4.0, 5.0); // Interval from 4.0 to 5.0 + + std::cout << "Interval Matrix M: \n" << M << std::endl; + + return 0; + } + +In this example, `M1` and `M2` are matrices of `Interval` objects, and we perform matrix addition as usual. The result, `M3`, is also a matrix of intervals, where each entry is the sum of the corresponding interval values from `M1` and `M2`. + +This approach allows Codac to work with uncertain or constrained data efficiently, benefiting from Eigen’s optimizations and providing robust handling of interval-based computations. + + + +Operations Between a Regular Matrix and an Interval Matrix +---------------------------------------------------------- + +In **Codac**, it is possible to perform operations between a regular matrix (e.g., `Eigen::MatrixXd`) and an interval matrix (e.g., `Eigen::Matrix`) using Eigen’s expression templates. This is particularly useful when you want to combine exact values from a standard matrix with uncertain or bounded values represented by intervals. + +Eigen allows element-wise operations between matrices of different types (like `MatrixXd` and `IntervalMatrix`), and it will automatically handle the differences in data types. The result of such operations will be an interval matrix, where each element of the resulting matrix will be the corresponding interval from the interval matrix plus (or minus, or multiplied by, etc.) the corresponding value from the regular matrix. + +### Example: Adding a Regular Matrix to an Interval Matrix + +.. code-block:: cpp + + #include + #include // Codac-specific headers + #include + + int main() { + // Define a regular matrix + Eigen::MatrixXd A(2, 2); + A(0, 0) = 1.0; + A(0, 1) = 2.0; + A(1, 0) = 3.0; + A(1, 1) = 4.0; + + // Define an interval matrix + Eigen::Matrix B; + B(0, 0) = Interval(1.0, 2.0); // Interval from 1.0 to 2.0 + B(0, 1) = Interval(2.0, 3.0); // Interval from 2.0 to 3.0 + B(1, 0) = Interval(3.0, 4.0); // Interval from 3.0 to 4.0 + B(1, 1) = Interval(4.0, 5.0); // Interval from 4.0 to 5.0 + + // Add a regular matrix to an interval matrix + Eigen::Matrix C = B + A.cast(); + + std::cout << "Result of B + A: \n" << C << std::endl; + return 0; + } + + +In this example, we perform an element-wise addition between the regular matrix A (of type Eigen::MatrixXd) and the interval matrix B (of type Eigen::Matrix). Since A contains exact values, and B contains intervals, we first cast the regular matrix A to an interval matrix using A.cast(). This operation converts each element of A into a corresponding interval, where each element is represented by an interval with a lower and upper bound equal to the value of A. + +The result is stored in matrix C, which will be an interval matrix where each element represents the sum of the corresponding interval from B and the value from A (now cast to intervals). + + + +Inversion of Interval Matrices +------------------------------ + +Eigen provides several algorithms for matrix inversion, such as the **LU decomposition** and **QR decomposition**, which can be applied to interval matrices. However, when working with interval matrices, the results of matrix inversion may not always be guaranteed due to the nature of interval arithmetic. + +Since interval matrices represent ranges of values rather than exact numbers, the inversion of an interval matrix is inherently more complex. Inverting an interval matrix involves computing bounds for each element in the inverse matrix, which may not always be precise or reliable. Additionally, the matrix may not be invertible if it contains intervals that lead to a singular or nearly singular matrix, which can cause issues in numerical stability. + +### Example: Inversion of an Interval Matrix + +.. code-block:: cpp + + Eigen::Matrix A; + A(0, 0) = Interval(1.0, 2.0); + A(0, 1) = Interval(2.0, 3.0); + A(1, 0) = Interval(3.0, 4.0); + A(1, 1) = Interval(4.0, 5.0); + + // Attempting to invert an interval matrix + Eigen::Matrix A_inv = A.inverse(); + std::cout << "Inverse of A: \n" << A_inv << std::endl; diff --git a/doc/manual/manual/pavers/index.rst b/doc/manual/manual/pavers/index.rst new file mode 100644 index 000000000..f259364d1 --- /dev/null +++ b/doc/manual/manual/pavers/index.rst @@ -0,0 +1,8 @@ +Pavers +====== + +.. toctree:: + + The SIVIA algorithm + Pavers for contractors and separators + structures/index.rst \ No newline at end of file diff --git a/doc/manual/manual/pavers/structures/index.rst b/doc/manual/manual/pavers/structures/index.rst new file mode 100644 index 000000000..ff14312db --- /dev/null +++ b/doc/manual/manual/pavers/structures/index.rst @@ -0,0 +1,7 @@ +Paving structures +================= + +.. toctree:: + + RegularPaving + NonRegularPaving \ No newline at end of file diff --git a/doc/manual/manual/references/authors.rst b/doc/manual/manual/references/authors.rst new file mode 100644 index 000000000..04d50a937 --- /dev/null +++ b/doc/manual/manual/references/authors.rst @@ -0,0 +1,38 @@ +Contributors +============ + +The **Codac** library has been developed and improved with the invaluable contributions of many individuals. Each contributor has played a vital role in making this library more powerful and user-friendly. Below, we list the main contributors and their contributions to the project. Thank you all for your efforts! + +### Core Contributors + +- **Contributor 1 Name** + Role: Lead Developer + Contribution: Overview of key algorithms, architecture design, and core development. + +- **Contributor 2 Name** + Role: Developer + Contribution: Implemented the Interval Matrix functionalities, optimization routines, and bug fixes. + +- **Contributor 3 Name** + Role: Documentation Specialist + Contribution: Authored user manuals, tutorials, and contributed to the code comments. + +- **Contributor 4 Name** + Role: Research Contributor + Contribution: Provided theoretical insights into the mathematical foundations of the library. + +### Special Thanks + +We would also like to extend our thanks to the following individuals and organizations for their support, feedback, and contributions to the success of **Codac**: + +- **Supporter 1 Name** + Contribution: Tested early versions and provided valuable feedback on usability. + +- **Organization Name** + Contribution: Financial support and resources for maintaining the project. + +### How to Contribute + +We welcome contributions from anyone interested in improving **Codac**. If you'd like to get involved, please check out our contribution guidelines or open a pull request on GitHub. Your help, no matter how small, is greatly appreciated! + +Thank you to all contributors for their time and effort in making **Codac** a powerful and versatile library. diff --git a/doc/manual/manual/references/index.rst b/doc/manual/manual/references/index.rst new file mode 100644 index 000000000..e666d216b --- /dev/null +++ b/doc/manual/manual/references/index.rst @@ -0,0 +1,8 @@ +References +========== + +.. toctree:: + + papers.rst + authors.rst + How to cite Codac \ No newline at end of file diff --git a/doc/manual/manual/references/papers.rst b/doc/manual/manual/references/papers.rst new file mode 100644 index 000000000..e04f2cdcb --- /dev/null +++ b/doc/manual/manual/references/papers.rst @@ -0,0 +1,16 @@ +Related papers +============== + +This section provides the bibliographic references related to the library. You can add your references as necessary. Below is a placeholder list where you can insert relevant publications, research papers, or documentation that have been referenced during the development of the library. + +### Key References + +- **Reference 1**: Description of the first key reference. + +- **Reference 2**: Description of the second key reference. + +- **Reference 3**: Description of the third key reference. + +- **Reference 4**: Description of the fourth key reference. + +Feel free to update this section with additional references relevant to the usage, design, or theoretical background of the library. diff --git a/doc/manual/manual/separators/analytic/index.rst b/doc/manual/manual/separators/analytic/index.rst new file mode 100644 index 000000000..40fa89f12 --- /dev/null +++ b/doc/manual/manual/separators/analytic/index.rst @@ -0,0 +1,7 @@ +Analytic separators +=================== + +.. toctree:: + + SepInverse + SepTransform \ No newline at end of file diff --git a/doc/manual/manual/separators/basic/index.rst b/doc/manual/manual/separators/basic/index.rst new file mode 100644 index 000000000..02fcde469 --- /dev/null +++ b/doc/manual/manual/separators/basic/index.rst @@ -0,0 +1,6 @@ +Basic separators +================ + +.. toctree:: + + SepCtcPair \ No newline at end of file diff --git a/doc/manual/manual/separators/contractors/index.rst b/doc/manual/manual/separators/contractors/index.rst new file mode 100644 index 000000000..79fd5cd2a --- /dev/null +++ b/doc/manual/manual/separators/contractors/index.rst @@ -0,0 +1,6 @@ +Contractors obtained from separators +==================================== + +.. toctree:: + + CtcInnerOuter \ No newline at end of file diff --git a/doc/manual/manual/separators/geometric/index.rst b/doc/manual/manual/separators/geometric/index.rst new file mode 100644 index 000000000..ad1c47897 --- /dev/null +++ b/doc/manual/manual/separators/geometric/index.rst @@ -0,0 +1,8 @@ +Geometrical separators +====================== + +.. toctree:: + + SepPolarCart or SepCartPolar + SepPolygon + SepEllipse \ No newline at end of file diff --git a/doc/manual/manual/separators/index.rst b/doc/manual/manual/separators/index.rst new file mode 100644 index 000000000..61ea47172 --- /dev/null +++ b/doc/manual/manual/separators/index.rst @@ -0,0 +1,14 @@ +Separators +========== + +.. toctree:: + + What are separators? + The Sep class + basic/index.rst + set/index.rst + analytic/index.rst + geometric/index.rst + shape/index.rst + contractors/index.rst + Towards thick separators \ No newline at end of file diff --git a/doc/manual/manual/separators/set/index.rst b/doc/manual/manual/separators/set/index.rst new file mode 100644 index 000000000..0f90c5de9 --- /dev/null +++ b/doc/manual/manual/separators/set/index.rst @@ -0,0 +1,12 @@ +Set separators +============== + +.. toctree:: + + SepUnion + SepInter + SepQInter + SepCartProd + SepProj + SepNot + SepAction \ No newline at end of file diff --git a/doc/manual/manual/separators/shape/index.rst b/doc/manual/manual/separators/shape/index.rst new file mode 100644 index 000000000..0afa3d361 --- /dev/null +++ b/doc/manual/manual/separators/shape/index.rst @@ -0,0 +1,8 @@ +Shape separators +================ + +.. toctree:: + + SepCtcBoundary + SepWrapper + SepImage \ No newline at end of file diff --git a/doc/manual/manual/tools/index.rst b/doc/manual/manual/tools/index.rst index 86f6b4682..c6fe0617c 100644 --- a/doc/manual/manual/tools/index.rst +++ b/doc/manual/manual/tools/index.rst @@ -9,5 +9,4 @@ Tools serialization.rst registration.rst octasym.rst - sampled_traj_npz.rst threading.rst \ No newline at end of file diff --git a/doc/manual/manual/tools/sampled_traj_npz.rst b/doc/manual/manual/tools/sampled_traj_npz.rst deleted file mode 100644 index 822ce9b6e..000000000 --- a/doc/manual/manual/tools/sampled_traj_npz.rst +++ /dev/null @@ -1,66 +0,0 @@ -.. _sec-trajectories-sampledtraj: - -Sampled trajectories -==================== - -In Python, the ``SampledTraj`` name is a convenience wrapper that mimics the -C++ template deduction style. Depending on the provided samples, it builds and -returns one of the following objects: - -- ``SampledTraj_Scalar`` -- ``SampledTraj_Vector`` -- ``SampledTraj_Matrix`` - -This keeps the user-facing syntax short and consistent with ``AnalyticTraj``. - - -Loading a sampled trajectory from a ``.npz`` file -------------------------------------------------- - -A sampled trajectory cannot be constructed directly from a file path. -The recommended workflow is: - -1. load the ``.npz`` file with ``numpy.load``; -2. extract the arrays containing the sampling times and sampled values; -3. call ``SampledTraj(t, x)``. - -The wrapper then deduces the appropriate trajectory type from ``x``. - -.. tabs:: - - .. group-tab:: Python - - .. code-block:: python - - import numpy as np - from codac import * - - data = np.load("traj_vec.npz") - traj = SampledTraj(data["t"], data["x"]) - - print(type(traj)) - -In the above example, if ``x`` is a 2D array of shape ``(N,n)``, then -``traj`` is a ``SampledTraj_Vector``. - -Another example: - -.. tabs:: - - .. group-tab:: Python - - .. code-block:: python - - import numpy as np - from codac import * - - t = np.array([0.0, 0.5, 1.0, 1.5]) - x = np.array([ - [0.0, 1.0], - [0.5, 1.5], - [1.0, 2.0], - [1.5, 2.5], - ]) - - traj = SampledTraj(t, x) - print(traj) # outputs: SampledTraj. [0, 1.5]↦[[0, 1.5][1, 2.5]], 4 pts \ No newline at end of file diff --git a/doc/manual/manual/tools/serialization.rst b/doc/manual/manual/tools/serialization.rst index b93c3b5bd..d67ae973b 100644 --- a/doc/manual/manual/tools/serialization.rst +++ b/doc/manual/manual/tools/serialization.rst @@ -18,10 +18,13 @@ The serialization system currently supports: - Built-in types (*e.g.*, ``int``, ``double``, ``float``) that are *trivially copyable*. The raw memory of the variables is directly written to or read from the stream ; - ``Interval`` objects ; - Matrix structures such as ``[Interval]Matrix``, ``[Interval]Vector``, and ``[Interval]Row``, with either scalar or ``Interval`` elements ; -- Any ``SampledTraj`` objects, as long as ``serialize`` functions are defined for type ``T``. +- Any ``SampledTraj`` objects, as long as ``serialize`` functions are defined for type ``T`` (see :ref:`sec-trajectories-sampledtraj`). All functions assume that the given streams are open and valid for the requested operation. +``AnalyticTraj`` is not listed among the supported serialized types: sample an analytic trajectory to a ``SampledTraj`` if you need to save discrete values (the defining analytic expression will not be preserved). +For trajectory-specific examples (including ``.npz`` and CSV), see :ref:`sec-trajectories-io`. + Example of use -------------- diff --git a/doc/manual/manual/topology/index.rst b/doc/manual/manual/topology/index.rst new file mode 100644 index 000000000..82ff33fcf --- /dev/null +++ b/doc/manual/manual/topology/index.rst @@ -0,0 +1,7 @@ +Topology +======== + +.. toctree:: + + Extract connected subsets from pavings + Topological degree: verify zeros of an inclusion function \ No newline at end of file diff --git a/doc/manual/manual/tubes/analytictraj.rst b/doc/manual/manual/tubes/analytictraj.rst new file mode 100644 index 000000000..6c749f97a --- /dev/null +++ b/doc/manual/manual/tubes/analytictraj.rst @@ -0,0 +1,102 @@ +.. _sec-trajectories-analytictraj: + +Analytic trajectories +===================== + + Main author: `Simon Rohou `_ + +``AnalyticTraj`` represents a trajectory defined by a function of time rather than by stored samples. It combines a temporal domain and an ``AnalyticFunction`` with one scalar input. + +Construction +------------ + +Define a ``ScalarVar`` for time, create an ``AnalyticFunction`` and pass the domain before the function to ``AnalyticTraj``. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-analytic-create-beg] + :end-before: [traj-analytic-create-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-analytic-create-beg] + :end-before: [traj-analytic-create-end] + :dedent: 4 + +Evaluation +---------- + +``x(t)`` evaluates the function at a given time. ``x([a,b])`` computes an interval enclosure over the time interval using, by default, the intersection of the natural and centered forms to reduce overestimation. If the centered form is unavailable, only the natural evaluation is used. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-analytic-eval-beg] + :end-before: [traj-analytic-eval-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-analytic-eval-beg] + :end-before: [traj-analytic-eval-end] + :dedent: 4 + +Sampling +-------- + +``sampled(dt)`` constructs a ``SampledTraj`` at regular instants, including the final time. The resulting interpolation is generally different from the original analytic function between samples. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-analytic-sampled-beg] + :end-before: [traj-analytic-sampled-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-analytic-sampled-beg] + :end-before: [traj-analytic-sampled-end] + :dedent: 4 + +Other operations +---------------- + +``tdomain()``, ``codomain()``, ``size()`` and ``truncate_tdomain()`` provide the usual trajectory properties. ``sampled_as(reference)`` evaluates at the reference timestamps. ``as_function()`` embeds the trajectory into an analytic expression. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-analytic-advanced-beg] + :end-before: [traj-analytic-advanced-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-analytic-advanced-beg] + :end-before: [traj-analytic-advanced-end] + :dedent: 4 + +For numerical integration with ``primitive(dt)``, see :doc:`trajectory_operations`. An analytic trajectory may also initialize a :doc:`slicedtube`. Its symbolic expression is not serialized directly. See :doc:`trajectory_io`. diff --git a/doc/manual/manual/tubes/data.csv b/doc/manual/manual/tubes/data.csv new file mode 100644 index 000000000..c2a846cd9 --- /dev/null +++ b/doc/manual/manual/tubes/data.csv @@ -0,0 +1,4 @@ +t;value +0;1.5 +1;2. +2;2.5 diff --git a/doc/manual/manual/tubes/index.rst b/doc/manual/manual/tubes/index.rst index e1baee6dc..d7dd71189 100644 --- a/doc/manual/manual/tubes/index.rst +++ b/doc/manual/manual/tubes/index.rst @@ -1,66 +1,62 @@ -.. _sec-domains-tubes: +.. _sec-tubes-trajectories: -Tubes -===== +Tubes & Trajectories +==================== Main author: `Simon Rohou `_ -Introduction ------------- +Codac provides trajectories to represent time-dependent values and tubes to represent time-dependent sets. -A tube is a set of trajectories. More precisely, if :math:`\mathcal{F}(\mathbb{R}\to\mathbb{R}^n)` denotes the set of trajectories with values in :math:`\mathbb{R}^n`, a tube :math:`\mathbb{X}(\cdot)` is a set-valued map +| A trajectory :math:`x(\cdot):[t_0,t_f]\rightarrow\mathbb{R}^n` associates a single value with each time. +| A tube is a set-valued function defined as .. math:: - \mathbb{X}(\cdot): \mathbb{R} \to \mathcal{P}(\mathbb{R}^n) + [x](\cdot):[t_0,t_f]\rightarrow\mathcal{P}(\mathbb{R}^n), -such that +where :math:`\mathcal{P}(\mathbb{R}^n)` denotes the power set of :math:`\mathbb{R}^n`. In Codac, these sets are represented by a configurable domain type, such as a box or a polygon. -.. math:: - \mathbf{x}(\cdot)\in\mathbb{X}(\cdot) - \;\Longleftrightarrow\; - \forall t,\ \mathbf{x}(t)\in\mathbb{X}(t). +.. _sec-trajectories: + +Trajectories +------------ -In other words, a tube encloses trajectories pointwise in time: for every time :math:`t`, the value :math:`\mathbf{x}(t)` must belong to the set :math:`\mathbb{X}(t)`. This definition is intentionally general. It does not encode additional temporal information such as continuity, monotonicity, or derivative constraints. Such properties may be handled separately, for instance by contractors or by combining several tubes. +Two representations are available. ``SampledTraj`` stores time/value samples and interpolates linearly between them, while ``AnalyticTraj`` evaluates expressions of time through an ``AnalyticFunction``. Both support scalar, vector and matrix trajectories. -This point of view naturally extends interval and box representations. In the most common cases, :math:`\mathbb{X}(t)` is an interval, a box, or a matrix of intervals, but in principle it may be any codomain type for which suitable set operations are available. +Trajectories are intended to represent approximations of solutions or measured signals. Guaranteed computations involving time-dependent quantities rely on tubes to account for uncertainties. -In Codac C++, this generality is reflected by the template ``SlicedTube``. In principle, ``T`` can be any codomain type compatible with the sliced-tube algorithms. -The library naturally provides and primarily uses: +.. toctree:: + :maxdepth: 1 -* ``SlicedTube``, -* ``SlicedTube``, -* ``SlicedTube``. + sampledtraj + analytictraj + trajectory_operations + trajectory_io -These are the most common tube types in practice. In Python and Matlab, only these standard sliced-tube classes are currently exposed through the bindings. -In C++, instantiations such as ``SlicedTube`` are allowed, provided the required tube operations are available for that domain. -This chapter documents the *sliced tube* implementation currently available in Codac. In this implementation, a tube is represented over an explicit temporal partition stored in a :class:`~codac.TDomain`. Each temporal element is a :class:`~codac.TSlice`, and each application-level value over one temporal element is represented by a :class:`~codac.Slice`. The main user-facing object is therefore a :class:`~codac.SlicedTube`. +.. _sec-tubes: -.. figure:: ./tube_slices.png - :width: 70% - - An example of sliced tube :math:`[x](\cdot)`, as implemented in Codac. The tube is made as a list of interval or box slices. In practice, the sampling :math:`\delta` is not necessarily constant. +Tubes +----- -Compared with the previous Codac v1 implementation, these tubes are not only functions of time: they also have shared data structures built on a common temporal partition. Refining the time partition of one tube may therefore update the internal slice structure of all tubes attached to the same :class:`~codac.TDomain`. This design is central to the current implementation. It simplifies the user experience: a temporal modification applied to one tube is implicitly propagated to the others, so multi-tube operations such as arithmetic combinations or contractions can still be performed easily on a consistent shared time basis. +A ``SlicedTube`` represents a tube over a temporal partition :class:`~codac.TDomain`. Each time element is a :class:`~codac.TSlice`, with an associated :class:`~codac.Slice` for each tube. Several tubes may share one temporal partition, so refining it also updates the structure of the other tubes. -Conceptually, the data model is: +The principal codomains are ``Interval``, ``IntervalVector`` and ``IntervalMatrix``, which represent scalar, vector and matrix sliced tubes. For these sliced tubes, contractors can reduce the enclosures using observations and dynamical constraints without discarding feasible trajectories, making them particularly useful for guaranteed state estimation. -.. code-block:: text +.. figure:: ./tube_slices.png + :width: 70% - TDomain - ├── TSlice #0 ----> Slice for tube x - │ └-> Slice for tube y - ├── TSlice #1 ----> Slice for tube x - │ └-> Slice for tube y - └── ... - + A sliced tube :math:`[x](\cdot)`, represented by interval enclosures over a temporal partition. -This chapter is organized as follows: +C++ also supports compatible custom codomains, such as ``ConvexPolygon`` or ``Ellipsoid``. .. toctree:: - :maxdepth: 1 + :maxdepth: 1 + + tdomain + slice + slicedtube + tube_examples - tdomain - slicedtube \ No newline at end of file +See also :ref:`sec-tools-serialization` and :doc:`../visualization/functions`. diff --git a/doc/manual/manual/tubes/old/trajectories.rst b/doc/manual/manual/tubes/old/trajectories.rst new file mode 100644 index 000000000..a39f96bfb --- /dev/null +++ b/doc/manual/manual/tubes/old/trajectories.rst @@ -0,0 +1,19 @@ +.. _sec-trajectories: + +Trajectories +============ + +A trajectory describes a time-dependent value :math:`x(\cdot):[t_0,t_f]\to\mathbb{R}^n`. Two representations are available: + +* :doc:`sampledtraj` stores time/value samples and interpolates linearly between them. +* :doc:`analytictraj` evaluates a known expression of time through an ``AnalyticFunction``. + +``SampledTraj`` is useful for numerical signals, while ``AnalyticTraj`` represents trajectories defined by analytical expressions. Both support scalar, vector and matrix trajectories. In Codac, trajectories are intended to represent approximations of solutions or measured signals, while tubes are used to perform guaranteed computations by enclosing uncertainties. + + +The following pages describe their construction, evaluation, common operations, and input/output: + +* :doc:`sampledtraj` +* :doc:`analytictraj` +* :doc:`trajectory_operations` +* :doc:`trajectory_io` diff --git a/doc/manual/manual/tubes/old/trajectory_reference.rst b/doc/manual/manual/tubes/old/trajectory_reference.rst new file mode 100644 index 000000000..6c5f4414f --- /dev/null +++ b/doc/manual/manual/tubes/old/trajectory_reference.rst @@ -0,0 +1,60 @@ +.. _sec-trajectories-reference: + +Trajectory API overview +======================= + +[retirer cette page et reverser éventuellement les fonctions vers d'autres pages] + +.. list-table:: Common operations + :header-rows: 1 + :widths: 31 34 35 + + * - Operation + - ``SampledTraj`` + - ``AnalyticTraj`` + * - ``tdomain()`` / ``codomain()`` / ``size()`` + - Time domain, range and output dimension + - Time domain, interval range and output dimension + * - ``x(t)`` / ``x([a,b])`` + - Interpolated value / range hull + - Analytic value / interval evaluation + * - ``sampled(dt)`` + - Adds samples; keeps original timestamps by default + - Samples the analytic expression + * - ``sampled_as(reference)`` + - Interpolates at reference timestamps + - Evaluates at reference timestamps + * - ``truncate_tdomain([a,b])`` + - Inserts boundary samples + - Restricts the evaluation domain + * - ``primitive(dt)`` + - Numerical integration + - Numerical integration + * - ``as_function()`` + - Embeds the interpolant in an analytic expression + - Embeds the analytic law in an expression + +.. list-table:: Additional ``SampledTraj`` operations + :header-rows: 1 + :widths: 36 64 + + * - Operation + - Description + * - ``set(value,time)`` / iteration + - Insert or replace a sample / traverse stored samples + * - ``nb_samples()`` + - Number of distinct timestamps; ``size()`` gives the codomain dimension + * - ``x[i]`` / ``subvector(i,j)`` + - Extract components of a vector trajectory + * - ``shift_tdomain(dt)`` / ``stretch_tdomain([a,b])`` + - Translate or rescale timestamps + * - ``derivative()`` / ``primitive()`` + - Numerical derivative / trapezoidal integral on existing samples + * - ``traj_cart_prod(...)`` + - Combine trajectories with identical timestamps + * - ``continuous_traj(x)`` + - Correct apparent jumps in a periodic scalar signal + +The C++ API also offers ``same_sampling()`` and ``as_scalar_trajs()``.[placer cette information dans une section dédiée (j'imagine dans une des pages précédentes)] Some template overloads differ from the Python bindings; for example, ``sampled_as()`` accepts a reference with another codomain type in C++, whereas the supplied Python bindings require matching types [trop technique pour être d'intérêt ici, à retirer]. For ``traj_eval()``, Python supports homogeneous scalar or vector inputs, while C++ also supports mixed arguments. [à placer dans la section d'évaluation appropriée] + +See :doc:`sampledtraj`, :doc:`analytictraj` and :doc:`trajectory_operations` for examples and the associated semantics. diff --git a/doc/manual/manual/tubes/old/tube_examples.rst b/doc/manual/manual/tubes/old/tube_examples.rst new file mode 100644 index 000000000..e9b1de5d5 --- /dev/null +++ b/doc/manual/manual/tubes/old/tube_examples.rst @@ -0,0 +1,159 @@ +.. _sec-domains-tubes-examples: + +Programming examples +==================== + + Main author: `Simon Rohou `_ + +The following examples illustrate basic operations on sliced tubes. + +Example 1: building and evaluating a scalar tube +------------------------------------------------ + +The following example builds a scalar tube over a sampled temporal domain and assigns different values on successive temporal intervals. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-1-beg] + :end-before: [tube-example-1-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-1-beg] + :end-before: [tube-example-1-end] + :dedent: 4 + + + +Example 2: refining a temporal domain from a tube update +-------------------------------------------------------- + +Local assignments refine the shared temporal partition when necessary. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-2-beg] + :end-before: [tube-example-2-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-2-beg] + :end-before: [tube-example-2-end] + :dedent: 4 + +The same partition is updated for all tubes sharing the temporal domain. + +Example 3: tube from an analytic function +----------------------------------------- + +For :math:`f(t)=10\cos(t)+t`, a ``SlicedTube`` encloses the function over each time slice rather than evaluating it only at isolated timestamps. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-3-beg] + :end-before: [tube-example-3-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-3-beg] + :end-before: [tube-example-3-end] + :dedent: 4 + + +Example 4: derivative-aware slice reasoning +------------------------------------------- + +The following pattern uses a derivative tube and ``CtcDeriv`` to sharpen a scalar slice. The slice can then build a polygonal enclosure used for refined evaluation or inversion. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-4-beg] + :end-before: [tube-example-4-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-4-beg] + :end-before: [tube-example-4-end] + :dedent: 4 + +The derivative constraint can narrow the tube before evaluating individual slices. + +Example 5: integration and primitive tubes +------------------------------------------ + +The integration API supports point and interval queries and can also build a primitive tube. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-5-beg] + :end-before: [tube-example-5-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-5-beg] + :end-before: [tube-example-5-end] + :dedent: 4 + + + +Example 6: Cartesian product of tubes +------------------------------------- + +When several sliced tubes share the same temporal domain, you can build a vector-valued tube by stacking their codomains slice by slice. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-6-beg] + :end-before: [tube-example-6-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-6-beg] + :end-before: [tube-example-6-end] + :dedent: 4 + +All inputs must share the same temporal-domain object. + +For the analogous operation on ``SampledTraj`` objects, see :ref:`sec-trajectories-operations` (``traj_cart_prod``). diff --git a/doc/manual/manual/tubes/old/tube_io.rst b/doc/manual/manual/tubes/old/tube_io.rst new file mode 100644 index 000000000..45fd787f5 --- /dev/null +++ b/doc/manual/manual/tubes/old/tube_io.rst @@ -0,0 +1,60 @@ +.. _sec-domains-tubes-io: + +Tube input, output and visualization +==================================== + +[même commentaire que précédemment, je doute de la pertinence de cette page, il vaudrait mieux transférer vers les sections dédiées] + +From time series +---------------- + +A ``SlicedTube`` can enclose the interpolation of a ``SampledTraj`` on a selected ``TDomain``. Inflate it by a justified uncertainty bound when the tube is intended to enclose an unknown signal, rather than just the interpolant. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-parity-input-beg] + :end-before: [tube-parity-input-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-parity-input-beg] + :end-before: [tube-parity-input-end] + :dedent: 4 + +Inspecting and saving tubes +--------------------------- +[retirer cette section. On fera un jour les fonctions de serialization, mais pas maintenant. On retire] + +For inspection, slice bounds can be written to a text file. The example below exports the temporal supports, codomains and gate indicators. This is not a native tube serialization format. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-parity-export-beg] + :end-before: [tube-parity-export-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-parity-export-beg] + :end-before: [tube-parity-export-end] + :dedent: 4 + +The current binary ``serialize`` API supports ``SampledTraj`` but not direct serialization of ``SlicedTube`` or its shared ``TDomain``. Saving ``tube.mid()`` stores only an approximate trajectory, not the tube. See :doc:`trajectory_io` and :ref:`sec-tools-serialization`. + +Visualization +------------- + +Use ``Figure2D.plot_tube`` to draw interval enclosures over time, or ``Figure2D.plot_trajectory`` for a nominal signal. See :doc:`../visualization/functions`. diff --git a/doc/manual/manual/tubes/old/tube_reference.rst b/doc/manual/manual/tubes/old/tube_reference.rst new file mode 100644 index 000000000..3443e1f09 --- /dev/null +++ b/doc/manual/manual/tubes/old/tube_reference.rst @@ -0,0 +1,60 @@ +.. _sec-domains-tubes-reference: + +Tube API overview +================= + +[retirer cette page et reverser éventuellement les fonctions vers d'autres pages] + +.. list-table:: Temporal domain and slices + :header-rows: 1 + :widths: 38 62 + + * - Operation + - Description + * - ``create_tdomain([a,b],dt,with_gates)`` + - Create a shared time partition with optional gates + * - ``td->sample(t)`` / ``td->truncate([a,b])`` + - Refine or restrict the temporal partition + * - ``td->nb_tslices()`` / ``td->nb_tubes()`` + - Count temporal elements / attached tubes + * - ``td->tslice(t)`` / ``td->tslices_vector()`` + - Access temporal elements + * - ``s.t0_tf()`` / ``s.codomain()`` + - Read a slice's temporal support and interval enclosure + * - ``s.is_gate()`` / ``s.input_gate()`` / ``s.output_gate()`` + - Inspect gate information + +.. list-table:: ``SlicedTube`` operations + :header-rows: 1 + :widths: 41 59 + + * - Operation + - Description + * - ``SlicedTube(td,domain)`` / ``SlicedTube(td,f)`` + - Initialize from an interval codomain or an analytic function + * - ``SlicedTube(td,traj)`` + - Enclose a trajectory on the partition + * - ``x.tdomain()`` / ``x.nb_slices()`` / ``x.size()`` + - Shared partition / number of elements / output dimension + * - ``x.set(...)`` / ``x.inflate(r)`` + - Assign values / enlarge enclosures + * - ``x(t)`` / ``x([a,b])`` + - Evaluate over a time or an interval + * - ``x.invert(y)`` + - Find compatible time enclosures + * - ``x.integral(...)`` / ``x.primitive(...)`` + - Interval integration / primitive tube + * - ``x[i]`` / ``x.subvector(i,j)`` + - Extract components of vector tubes + * - ``x.mid()`` + - Construct a numerical midpoint trajectory + * - ``tube_cart_prod(...)`` + - Assemble tubes sharing one ``TDomain`` + * - ``f.tube_eval(...)`` + - Evaluate analytic functions on tube arguments + * - ``x.as_function()`` + - Use a tube in an analytic expression for forward evaluation + +Operations between tubes require a shared temporal-domain object, even when independently created domains contain identical partitions. The available overloads depend on the codomain type; some template operations are not exposed in Python. Direct binary serialization of ``SlicedTube`` is not currently available. + +See :doc:`tdomain`, :doc:`slice`, :doc:`slicedtube` and :doc:`tube_operations` for details and examples. diff --git a/doc/manual/manual/tubes/old/tubes.zip b/doc/manual/manual/tubes/old/tubes.zip new file mode 100644 index 000000000..f954d888b Binary files /dev/null and b/doc/manual/manual/tubes/old/tubes.zip differ diff --git a/doc/manual/manual/tubes/old/tubes_overview.rst b/doc/manual/manual/tubes/old/tubes_overview.rst new file mode 100644 index 000000000..42ac4e207 --- /dev/null +++ b/doc/manual/manual/tubes/old/tubes_overview.rst @@ -0,0 +1,17 @@ +Tubes +===== + + Main author: `Simon Rohou `_ + +A tube :math:`[x](\cdot)` represents the set of trajectories satisfying :math:`x(t)\in[x](t)` at every time. Its codomain is typically an interval or a box, and contractors can reduce these enclosures using observations and dynamical constraints without discarding feasible trajectories. + +A ``SlicedTube`` represents a tube over a temporal partition :class:`~codac.TDomain`. Each time element is a :class:`~codac.TSlice`, with an associated :class:`~codac.Slice` for each tube. Several tubes may share one temporal partition, so refining it also updates the structure of the other tubes. + +The principal codomains are ``Interval``, ``IntervalVector`` and ``IntervalMatrix``. They represent scalar, vector and matrix sliced tubes. C++ also supports compatible custom codomains. + +.. figure:: ./tube_slices.png + :width: 70% + + A sliced tube :math:`[x](\cdot)`, represented by interval enclosures over a temporal partition. + +See :doc:`tdomain` for the shared temporal partition, :doc:`slice` for gates and :doc:`slicedtube` for construction, evaluation and operations. Practical examples are given in :doc:`tube_examples`. diff --git a/doc/manual/manual/tubes/sampledtraj.rst b/doc/manual/manual/tubes/sampledtraj.rst new file mode 100644 index 000000000..ee15ccf1f --- /dev/null +++ b/doc/manual/manual/tubes/sampledtraj.rst @@ -0,0 +1,143 @@ +.. _sec-trajectories-sampledtraj: + +Sampled trajectories +==================== + + Main author: `Simon Rohou `_ + +``SampledTraj`` stores time/value pairs :math:`(t_i,x_i)` with distinct, ordered timestamps. Between samples, the trajectory is linearly interpolated: + +.. math:: + + x(t)=x_i+\frac{t-t_i}{t_{i+1}-t_i}(x_{i+1}-x_i),\qquad t\in[t_i,t_{i+1}]. + +The codomain ``T`` can be a real, a vector or a matrix. In both C++ and Python, the value type of ``SampledTraj`` can be inferred from the constructor arguments. + +Construction +------------ + +Create a sampled trajectory with ``set(value,time)`` or from a map of samples. Assigning a value at an existing timestamp replaces the previous one. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-create-beg] + :end-before: [traj-sampled-create-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-create-beg] + :end-before: [traj-sampled-create-end] + :dedent: 4 + +Evaluation and properties +------------------------- + +``x(t)`` interpolates at a scalar time. ``x([a,b])`` returns the interval hull of the interpolant over :math:`[a,b]`. ``tdomain()`` and ``codomain()`` give the temporal domain and the hull of the values. ``nb_samples()`` counts timestamps, whereas ``size()`` gives the codomain dimension. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-eval-beg] + :end-before: [traj-sampled-eval-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-eval-beg] + :end-before: [traj-sampled-eval-end] + :dedent: 4 + +Accessing samples and components +-------------------------------- + +Iteration returns the stored pairs :math:`(t_i,x_i)` in time order. For a vector trajectory, ``x[i]`` extracts a scalar component and ``subvector(i,j)`` extracts consecutive components. In C++, ``as_scalar_trajs()`` returns all scalar components. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-iterate-beg] + :end-before: [traj-sampled-iterate-end] + :dedent: 4 + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-components-beg] + :end-before: [traj-sampled-components-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-iterate-beg] + :end-before: [traj-sampled-iterate-end] + :dedent: 4 + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-components-beg] + :end-before: [traj-sampled-components-end] + :dedent: 4 + +Sampling +-------- + +``sampled(dt)`` adds samples on a regular temporal grid, retaining original samples by default. Set ``keep_original_values=False`` to keep only the requested grid. ``sampled_as(reference)`` evaluates the trajectory at the timestamps of another ``reference`` trajectory. In C++, ``same_sampling()`` checks whether two trajectories share the same timestamps. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-grid-beg] + :end-before: [traj-sampled-grid-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-grid-beg] + :end-before: [traj-sampled-grid-end] + :dedent: 4 + +Changing the time domain +------------------------ + +``truncate_tdomain([a,b])`` restricts the domain and inserts interpolated values at its endpoints. ``shift_tdomain(dt)`` translates timestamps. ``stretch_tdomain([a,b])`` rescales them to a new temporal domain. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-sampled-time-beg] + :end-before: [traj-sampled-time-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-sampled-time-beg] + :end-before: [traj-sampled-time-end] + :dedent: 4 + +See also: :doc:`trajectory_operations` and :doc:`trajectory_io`. diff --git a/doc/manual/manual/tubes/slice.rst b/doc/manual/manual/tubes/slice.rst new file mode 100644 index 000000000..412ed0bb6 --- /dev/null +++ b/doc/manual/manual/tubes/slice.rst @@ -0,0 +1,119 @@ +.. _sec-tubes-slice: + +The Slice and TSlice classes +============================ + + Main author: `Simon Rohou `_ + +At the implementation level, sliced tubes are built from two complementary classes: + +* :class:`~codac.TSlice`, which stores one temporal support, +* :class:`~codac.Slice`, which stores one codomain over that support for one given tube. + +TSlice: one temporal element shared by all tubes +------------------------------------------------ + +A :class:`~codac.TSlice` inherits from :class:`~codac.Interval` and represents one temporal interval in a :class:`~codac.TDomain`. It may be a regular slice :math:`[t_i,t_{i+1}]` or a gate :math:`[t_i,t_i]`. It also stores the map from ``SlicedTubeBase*`` to the corresponding :class:`~codac.SliceBase` objects. In other words, a ``TSlice`` is the shared temporal node to which all tube-specific slices are attached. + +The method ``is_gate()`` simply checks whether the temporal interval is degenerate. The method ``slices()`` provides access to the attached codomain slices. + +Slice: one codomain over one temporal support +--------------------------------------------- + +A :class:`~codac.Slice` is the codomain carried by one sliced tube over one temporal interval. The codomain type is typically :class:`~codac.Interval` or :class:`~codac.IntervalVector`, and the class uses protected inheritance from this codomain type so that updates can be controlled and, if necessary, propagated to adjacent gates. + +Each slice knows: + +* its parent tube, +* its temporal interval ``t0_tf()``, +* its previous and next slices, +* its codomain, +* whether its temporal support is a gate. + +Input and output gates +---------------------- + +A central concept in the current implementation is the distinction between the *interior codomain* of a slice and its *endpoint values*. For a non-gate slice, endpoint values are obtained through + +* ``input_gate()`` +* ``output_gate()`` + +If an explicit neighboring gate exists, its codomain is used. Otherwise, the endpoint enclosure is the intersection of adjacent slice codomains. This is what makes endpoint evaluation meaningful even when gates are not explicitly represented. + +Without explicit gates, evaluating a tube at a slice boundary returns the intersection of the neighboring slice values. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [slice-endpoint-example-beg] + :end-before: [slice-endpoint-example-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [slice-endpoint-example-beg] + :end-before: [slice-endpoint-example-end] + :dedent: 4 + + +Evaluation and inversion at slice level +--------------------------------------- + +A scalar or vector slice can be evaluated with: + +* ``s(t)`` at one time, +* ``s([ta,tb])`` over a temporal interval. + +At the lower bound, evaluation returns the input gate. At the upper bound, it returns the output gate. Inside the slice, it returns the interior codomain. Outside the temporal support, it returns the unbounded codomain value. + +The inversion API mirrors this structure: + +* ``invert(y, t)`` computes a local inverse image, +* ``invert(y, v, t)`` uses a derivative slice for a sharper inverse image. + +The following example illustrates inversion over a selected temporal interval. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [slice-invert-example-beg] + :end-before: [slice-invert-example-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [slice-invert-example-beg] + :end-before: [slice-invert-example-end] + :dedent: 4 + + +Derivative-aware polygonal model +-------------------------------- + +For scalar codomains, and componentwise for vector codomains, a slice can build a polygonal enclosure from: + +* its temporal support, +* its input and output gates, +* a derivative slice. + +This is exposed by: + +* ``polygon_slice(v)`` for scalar slices, +* ``polygon_slice_i(v, i)`` for vector slices. + +This polygonal enclosure is used in derivative-aware evaluation and inversion. + +Enclosed bounds +--------------- + +The method ``enclosed_bounds(t)`` returns a pair of codomain objects whose components enclose the reachable lower and upper bounds over the queried temporal interval. This is useful when you want more structural information than the plain union returned by a simple evaluation. diff --git a/doc/manual/manual/tubes/slicedtube.rst b/doc/manual/manual/tubes/slicedtube.rst index 01c21cbac..c596387c3 100644 --- a/doc/manual/manual/tubes/slicedtube.rst +++ b/doc/manual/manual/tubes/slicedtube.rst @@ -1,18 +1,17 @@ -.. _sec-domains-tubes-slicedtube: +.. _sec-tubes-slicedtube: -The SlicedTube class -==================== +Sliced tubes +============ Main author: `Simon Rohou `_ -A :class:`~codac.SlicedTube` is a tube defined over a shared :class:`~codac.TDomain`. -For each temporal slice stored in the temporal partition, the tube owns one :class:`~codac.Slice` object storing its codomain over that temporal support. The codomain type is typically :class:`~codac.Interval` or :class:`~codac.IntervalVector`, or any domain type defined by the user. The same temporal domain can be shared by many tubes. +A :class:`~codac.SlicedTube` is a tube defined over a shared :class:`~codac.TDomain`. For each temporal slice stored in the temporal partition, the tube owns one :class:`~codac.Slice` object storing its codomain over that temporal support. The codomain type is typically :class:`~codac.Interval` or :class:`~codac.IntervalVector`, or any domain type defined by the user. The same temporal domain can be shared by many tubes. Creating sliced tubes --------------------- -In Codac C++, :class:`~codac.SlicedTube` is a generic class template ``SlicedTube``. The codomain type ``T`` is not restricted to intervals or boxes: in principle, any domain type compatible with the sliced-tube API can be used. +In C++, :class:`~codac.SlicedTube` is a generic class template ``SlicedTube``. The codomain type ``T`` is not restricted to intervals or boxes: in principle, any domain type compatible with the sliced-tube API can be used. In practice, the most common sliced-tube types are: @@ -24,6 +23,8 @@ These standard tube types are also the ones exposed in the Python and Matlab bin In C++, template argument deduction is available for the constructors based on the arguments. In the examples below, comments make the deduced type explicit when useful. +For analytic and sampled trajectories, see :ref:`sec-trajectories`. For conversions and evaluations with analytic functions, see :ref:`sec-trajectories-operations`. + A sliced tube can be created from: * a constant codomain, @@ -50,6 +51,27 @@ A sliced tube can be created from: :dedent: 4 +A ``SlicedTube`` can also enclose a ``SampledTraj`` on a chosen temporal partition. To represent an uncertain physical signal, inflate it by a justified uncertainty bound. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-parity-input-beg] + :end-before: [tube-parity-input-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-parity-input-beg] + :end-before: [tube-parity-input-end] + :dedent: 4 + + .. figure:: interval_trajs.png :width: 70% @@ -62,7 +84,6 @@ Basic properties A sliced tube exposes: * ``size()`` for the codomain dimension, -* ``shape()`` for matrix-like codomain shapes, * ``tdomain()`` and ``t0_tf()`` from the base classes, * ``nb_slices()`` for the number of temporal elements, * ``codomain()`` for the global codomain hull, @@ -77,8 +98,7 @@ You can access the first and last slices directly: * ``first_slice()`` * ``last_slice()`` -and you can retrieve a slice from a temporal-domain iterator or from a ``TSlice`` pointer. -The class also defines custom iterators so that iterating on a ``SlicedTube`` yields application-level :class:`~codac.Slice` objects rather than raw ``TSlice`` objects. +and you can retrieve a slice from a temporal-domain iterator or from a ``TSlice`` pointer. The class also defines custom iterators so that iterating on a ``SlicedTube`` yields application-level :class:`~codac.Slice` objects rather than raw ``TSlice`` objects. .. tabs:: @@ -109,8 +129,7 @@ A sliced tube can be updated globally or locally: * ``set(codomain, [ta,tb])`` sets all temporal elements intersecting an interval, * ``set_ith_slice(codomain, i)`` sets one stored slice by index. -The key point is that local assignments may refine the underlying :class:`~codac.TDomain`. -For example, setting a value at one scalar time creates an explicit gate if necessary. Similarly, setting a value over an interval may create new temporal boundaries at the interval endpoints. +The key point is that local assignments may refine the underlying :class:`~codac.TDomain`. For example, setting a value at one scalar time creates an explicit gate if necessary. Similarly, setting a value over an interval may create new temporal boundaries at the interval endpoints. .. note:: @@ -138,7 +157,7 @@ For example, setting a value at one scalar time creates an explicit gate if nece Evaluation ---------- -A sliced tube :math:`[x](\cdot)` can be evaluated over a temporal interval :math:`[t]` with ``x(t)``. The implementation walks through all relevant temporal slices, evaluates each local slice, and unions the results. If the query interval is not included in the temporal domain, the result is the unbounded value of the codomain type (for interval tubes: :math:`[-\infty,\infty]`). +A sliced tube :math:`[x](\cdot)` can be evaluated over a temporal interval :math:`[t]` with ``x(t)``. The implementation walks through all relevant temporal slices, evaluates each local slice, and takes their interval hull. If the query interval is not included in the temporal domain, the result is the unbounded value of the codomain type (for interval tubes: :math:`[-\infty,\infty]`). When a derivative tube ``v`` is available, the overload ``x(t,v)`` uses per-slice derivative-aware evaluation and unions the resulting enclosures. This is available for ``Interval`` and ``IntervalVector`` codomain types. @@ -220,16 +239,18 @@ The following example computes the different connected components of the inversi :project: codac +.. _sec-domains-tubes-operations: + Tube operations --------------- Standard pointwise operations are available on sliced tubes and are applied slice by slice. -For two tubes, binary operators require both operands to share the same :class:`~codac.TDomain`. This makes tube expressions easy to combine once the tubes have been built on a common temporal support. +Binary operators on tubes require the same :class:`~codac.TDomain` object. This lets all operands share refinements of the temporal partition. The following operators are available: -* union and intersection: ``|`` and ``&``, +* interval hull and intersection: ``|`` and ``&``, * arithmetic operations: ``+``, ``-``, ``*``, ``/``, * in-place variants: ``|=``, ``&=``, ``+=``, ``-=``, ``*=``, ``/=``. @@ -274,6 +295,14 @@ For vector-valued codomains, pointwise arithmetic is also available. In particul :dedent: 4 +Cartesian products +------------------ + +``tube_cart_prod(x1,x2,...)`` combines scalar and vector tubes into a vector tube, provided they share the same ``TDomain``. The resulting dimension is the sum of the input dimensions. + +A short example combining scalar and vector tubes is given in :doc:`tube_examples`. + + .. _sec-domains-tubes-slicedtube-function-eval: Evaluating analytic functions on tubes @@ -285,8 +314,7 @@ An :class:`~codac.AnalyticFunction` can be evaluated on one or more sliced tubes [y]_k = [f]([x_1]_k, [x_2]_k, \ldots, [x_n]_k). -The result encloses :math:`f(x_1(t),\ldots,x_n(t))` at every time :math:`t`. -It shares the same ``TDomain`` as the input tubes, including its gates. All input tubes must share the same ``TDomain`` object, not merely have identical time bounds or discretizations. If the function is scalar-valued, ``tube_eval()`` returns a scalar tube. If it is vector-valued, ``tube_eval()`` returns a vector tube. In Python, the number of input tubes supported by ``tube_eval()`` is currently limited. All input tubes must be of the same kind (scalar or vector), whereas C++ also supports mixed types. +The result encloses :math:`f(x_1(t),\ldots,x_n(t))` at every time :math:`t`. It shares the same ``TDomain`` as the input tubes, including its gates. All input tubes must share the same ``TDomain`` object, not merely have identical time bounds or discretizations. If the function is scalar-valued, ``tube_eval()`` returns a scalar tube. If it is vector-valued, ``tube_eval()`` returns a vector tube. In Python, the number of input tubes supported by ``tube_eval()`` is currently limited. All input tubes must be of the same kind (scalar or vector), whereas C++ also supports mixed types. In the following example, a two-argument function maps heading and speed tubes to a 2D velocity tube :math:`(v_x,v_y)=(v\cos\theta,v\sin\theta)`. A second function computes its squared Euclidean norm :math:`v_x^2+v_y^2`, returning a scalar tube: @@ -308,8 +336,7 @@ In the following example, a two-argument function maps heading and speed tubes t :end-before: [slicedtube-class-function-eval-end] :dedent: 4 -Unlike ``x(t)``, which evaluates a tube at a given time, ``f.tube_eval(x)`` evaluates an analytic function on an entire tube. It does not implicitly pass time to ``f``: if time is an argument, it must be supplied as an input tube. -For functions of time alone, ``SlicedTube(tdomain, f)`` directly evaluates ``f(t)`` over the temporal slices (see `Creating sliced tubes`_). +Unlike ``x(t)``, which evaluates a tube at a given time, ``f.tube_eval(x)`` evaluates an analytic function on an entire tube. It does not implicitly pass time to ``f``: if time is an argument, it must be supplied as an input tube. For functions of time alone, ``SlicedTube(tdomain, f)`` directly evaluates ``f(t)`` over the temporal slices (see `Creating sliced tubes`_). Integration and primitives @@ -337,8 +364,7 @@ The computation is reliable even for tubes defined from analytic functions, beca Inflation, extraction, and algebraic helpers -------------------------------------------- -A sliced tube can be inflated either by a constant radius or by a time-varying -sampled radius. Inflation is performed in place. +A sliced tube can be inflated either by a constant radius or by a time-varying sampled radius. Inflation is performed in place. .. tabs:: @@ -389,4 +415,6 @@ The extracted tubes keep the same temporal partition as the original one. Involving a tube in an analytic expression ------------------------------------------ -The method ``.as_function()`` wraps a sliced tube as an analytic operator so that it can be embedded inside analytic expressions. This is a convenient bridge between the tube API and the analytic-expression API, see :ref:`the section on temporal operators ` for its use. \ No newline at end of file +The method ``.as_function()`` wraps a sliced tube as an analytic operator so that it can be embedded inside analytic expressions. This is a convenient bridge between the tube API and the analytic-expression API, see :ref:`the section on temporal operators ` for its use. + +For visualization, use ``Figure2D.plot_tube`` to display interval enclosures over time. See :doc:`../visualization/functions`. diff --git a/doc/manual/manual/tubes/src.cpp b/doc/manual/manual/tubes/src.cpp index 0eb2c62af..c3d690c36 100644 --- a/doc/manual/manual/tubes/src.cpp +++ b/doc/manual/manual/tubes/src.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include +#include #include #include #include @@ -16,6 +19,10 @@ #include #include #include +#include +#include +#include +#include #include using namespace std; @@ -40,11 +47,11 @@ TEST_CASE("TDomain class - manual") size_t n = td->nb_tslices(); // 5 size_t m = td->nb_tubes(); // 0 initially - auto it = td->tslice(0.0); // [0] - it = td->tslice(0.1); // [0,0.5] - it = td->tslice(0.5); // [0.5] - it = td->tslice(0.6); // [0.5,1] - it = td->tslice(1.0); // [1] + auto it = td->tslice(0.); // [0] + it = td->tslice(0.1); // [0,0.5] + it = td->tslice(0.5); // [0.5] + it = td->tslice(0.6); // [0.5,1] + it = td->tslice(1.); // [1] // [tdomain-class-2-end] CHECK(n == 5); CHECK(m == 0); @@ -61,20 +68,20 @@ TEST_CASE("TDomain class - manual") { // [tdomain-class-4-beg] - auto td = create_tdomain({0,2}, 1.0, false); // false: without gates + auto td = create_tdomain({0,2}, 1., false); // false: without gates SlicedTube x(td, Interval(0,1)); SlicedTube v(td, Interval(-1,1)); - cout << x << endl; // outputs [0,2]->[0,1], 2 slices - cout << v << endl; // outputs [0,2]->[0,1], 2 slices + cout << x << endl; // codomain [0,1], 2 temporal elements + cout << v << endl; // codomain [-1,1], 2 temporal elements size_t n = td->nb_tslices(); // 2: [0,1],[1,2] x.set({0.5,1}, 1.3); // local update, will refine the partition at t=1.3 size_t m = td->nb_tslices(); // now 4: [0,1],[1,1.3],[1.3],[1.3,2] - cout << x << endl; // outputs [0,2]->[-1,1], 4 slices - cout << v << endl; // outputs [0,2]->[-1,1], 4 slices (v is also impacted by x.set(..)) + cout << x << endl; // codomain [0,1], 4 temporal elements + cout << v << endl; // codomain [-1,1], 4 temporal elements (shared partition refined) // [tdomain-class-4-end] CHECK(n == 2); CHECK(m == 4); @@ -82,7 +89,7 @@ TEST_CASE("TDomain class - manual") { // [tdomain-class-5-beg] - auto td = create_tdomain({0,3}, 1.0, false); + auto td = create_tdomain({0,3}, 1., false); SlicedTube x(td, Interval(0,1)); cout << td->nb_tubes() << endl; // 1 @@ -104,9 +111,9 @@ TEST_CASE("SlicedTube class - manual") auto td = create_tdomain({0,10}, 0.1, true); // Sliced tube from interval-type codomains - SlicedTube x(td, Interval(-1,1)); // x has type SlicedTube - SlicedTube y(td, IntervalVector(2)); // y has type SlicedTube - SlicedTube z(td, IntervalMatrix(2,2)); // z has type SlicedTube + SlicedTube x(td, Interval(-1,1)); // scalar tube: SlicedTube + SlicedTube y(td, IntervalVector(2)); // vector tube: SlicedTube + SlicedTube z(td, IntervalMatrix(2,2)); // matrix tube: SlicedTube // Explicit template notation remains possible: SlicedTube x2(td, Interval(-1,1)); @@ -114,8 +121,7 @@ TEST_CASE("SlicedTube class - manual") // From an analytic function ScalarVar t; AnalyticFunction f({t}, sin(t)); - SlicedTube xf(td, f); - // xf has type SlicedTube because f outputs scalar values + SlicedTube xf(td, f); // scalar tube: SlicedTube // From an analytic trajectory or sampled trajectory AnalyticTraj at({0,10}, AnalyticFunction({t},cos(t))); @@ -145,7 +151,7 @@ TEST_CASE("SlicedTube class - manual") { // [slicedtube-class-4-beg] - auto td = create_tdomain({0,3}, 1.0, false); + auto td = create_tdomain({0,3}, 1., false); SlicedTube x(td, Interval()); x.set({1,5}, {0,1}); @@ -155,12 +161,12 @@ TEST_CASE("SlicedTube class - manual") Interval y0 = x(0.5); // [1,5] Interval y1 = x(1.5); // [2,8] Interval y2 = x({0,3}); // [1,9] - Interval y3 = x(-1.0); // [-oo,oo] + Interval y3 = x(-1.); // [-oo,oo] // No explicit gates: boundary values come from adjacent-slice intersections - Interval y4 = x(1.0); // [2,5] - Interval y5 = x(2.0); // [6,8] - Interval y6 = x(3.0); // [6,9] + Interval y4 = x(1.); // [2,5] + Interval y5 = x(2.); // [6,8] + Interval y6 = x(3.); // [6,9] // [slicedtube-class-4-end] } @@ -197,7 +203,7 @@ TEST_CASE("SlicedTube class - manual") { // [slicedtube-class-6-beg] - auto td = create_tdomain({0,2}, 1.0, false); + auto td = create_tdomain({0,2}, 1., false); SlicedTube x(td, Interval(1,2)); SlicedTube y(td, Interval(-1,3)); @@ -218,7 +224,7 @@ TEST_CASE("SlicedTube class - manual") SlicedTube A(td, IntervalMatrix(2,2)); SlicedTube b(td, IntervalVector(2)); - auto y = A * b; // type: SlicedTube + auto y = A * b; // vector tube: SlicedTube // [slicedtube-class-7-end] } @@ -232,7 +238,7 @@ TEST_CASE("SlicedTube class - manual") ScalarVar h, v; AnalyticFunction f({h,v}, {v*cos(h), v*sin(h)}); - auto velocity = f.tube_eval(heading,speed); // SlicedTube + auto velocity = f.tube_eval(heading,speed); // vector tube: SlicedTube VectorVar p(2); AnalyticFunction g({p}, sqr(p[0])+sqr(p[1])); @@ -250,7 +256,7 @@ TEST_CASE("SlicedTube class - manual") // [slicedtube-class-8-beg] x.inflate(0.2); // constant inflation - SampledTraj rad({{0.0,0.11}, {1.0,0.3}, {2.0,0.2}}); + SampledTraj rad({{0.,0.11}, {1.,0.3}, {2.,0.2}}); x.inflate(rad); // time-varying inflation radius // [slicedtube-class-8-end] } @@ -261,9 +267,389 @@ TEST_CASE("SlicedTube class - manual") // [slicedtube-class-9-beg] // Component and subvector extraction - auto x0 = x[0]; // type: SlicedTube - auto x12 = x.subvector(1,2); // type: SlicedTube + auto x0 = x[0]; // scalar tube: SlicedTube + auto x12 = x.subvector(1,2); // vector tube: SlicedTube // [slicedtube-class-9-end] } -} \ No newline at end of file +} + + +TEST_CASE("Trajectory classes - manual") +{ + { + // [traj-sampled-create-beg] + SampledTraj depth; + depth.set(2., 0.); + depth.set(3., 1.); + depth.set(4., 2.); + + SampledTraj position; + position.set(Vector({0,1}), 0.); + position.set({1,2}, 1.); // equivalent shorter notation + position.set({2,3}, 2.); + // [traj-sampled-create-end] + CHECK(depth.nb_samples() == 3); + CHECK(position.size() == 2); + } + + { + // [traj-sampled-eval-beg] + SampledTraj x({{0.,2.}, {1.,4.}, {2.,3.}}); + double value = x(0.5); // 3. + Interval range = x({0.5,1.5}); // [3,4] + auto sample_count = x.nb_samples(); // 3 + auto output_size = x.size(); // 1 + // [traj-sampled-eval-end] + CHECK(value == 3); + CHECK(range == Interval(3,4)); + CHECK(sample_count == 3); + CHECK(output_size == 1); + + // [traj-sampled-iterate-beg] + for(const auto& [ti, xi] : x) + std::cout << ti << " -> " << xi << std::endl; + // [traj-sampled-iterate-end] + CHECK(value == 3.); + CHECK(sample_count == 3); + CHECK(output_size == 1); + CHECK(range.contains(3.)); + } + + { + // [traj-sampled-components-beg] + SampledTraj p({{0.,{0,1,2}}, {1.,{1,2,3}}}); + auto x = p[0]; + auto yz = p.subvector(1,2); + // [traj-sampled-components-end] + CHECK(yz.size() == 2); + + } + + { + // [traj-sampled-time-beg] + SampledTraj x({{0.,0.}, {1.,1.}, {2.,0.}}); + x.truncate_tdomain({0.5,1.5}); // retains interpolated endpoints + x.shift_tdomain(10.); // time domain now [10.5,11.5] + x.stretch_tdomain({0,1}); // rescale time, not values + // [traj-sampled-time-end] + CHECK(x.tdomain() == Interval(0,1)); + } + + { + // [traj-analytic-create-beg] + ScalarVar t; + AnalyticFunction f({t}, 2*sin(t)); + AnalyticTraj traj_f({0,2}, f); // scalar trajectory + + AnalyticFunction g({t}, {cos(t), sin(t)}); + AnalyticTraj traj_g({0,2}, g); // vector trajectory + // [traj-analytic-create-end] + + // [traj-analytic-eval-beg] + double y_point = traj_f(0.5); // scalar value + Interval y_interval = traj_f({0,1}); // interval enclosure + Vector p_point = traj_g(0.5); // vector value + // [traj-analytic-eval-end] + CHECK(y_interval.contains(y_point)); + CHECK(p_point.size() == 2); + + // [traj-analytic-sampled-beg] + SampledTraj f_samples = traj_f.sampled(0.1); + SampledTraj g_samples = traj_g.sampled(0.1); + // [traj-analytic-sampled-end] + CHECK(f_samples.nb_samples() >= 20); + CHECK(g_samples.size() == 2); + } + + { + // [traj-ops-calculus-beg] + SampledTraj speed({{0.,0}, {1.,1}, {2.,2}}); + auto acceleration = speed.derivative(); // numerical derivative + auto distance = speed.primitive(); // trapezoidal integral, p(0)=0 + auto doubled_speed = 2. * speed; // pointwise arithmetic + // [traj-ops-calculus-end] + CHECK(distance(2.) == 2.); + } + + { + // [traj-ops-cartprod-beg] + SampledTraj x1({0.,1}, {0.,1}); + SampledTraj x2({0.,1}, {2.,3}); + auto x = traj_cart_prod(x1,x2); + // [traj-ops-cartprod-end] + CHECK(x.size() == 2); + + // [traj-ops-continuous-beg] + SampledTraj heading({{0.,6.1}, {1.,0.1}, {2.,0.2}}); + auto continuous_heading = continuous_traj(heading); + // [traj-ops-continuous-end] + CHECK(continuous_heading.nb_samples() == 3); + } + + { + // [traj-ops-eval-beg] + SampledTraj theta({{0.,0}, {1.,0.1}, {2.,0.2}}); + SampledTraj v({{0.,1}, {1.,2}, {2.,3}}); + ScalarVar theta_var, v_var; + AnalyticFunction f({theta_var,v_var}, {v_var*cos(theta_var), v_var*sin(theta_var)}); + auto u = f.traj_eval(theta, v); + // [traj-ops-eval-end] + CHECK(u.size() == 2); + } + + { + // [traj-ops-asfunction-beg] + SampledTraj measurements({{0.,1}, {1.,2}, {2.,1.5}}); + auto measurement_fn = measurements.as_function(); + ScalarVar t; + AnalyticFunction energy({t}, sqr(measurement_fn(t))); + auto estimated_energy = energy.eval(0.5); + // [traj-ops-asfunction-end] + } + + { + // [traj-ops-tube-beg] + ScalarVar t; + AnalyticTraj traj({0,2}, AnalyticFunction({t}, sin(t))); + auto td = create_tdomain({0,2}, 0.1); + SlicedTube tube(td, traj); + tube.inflate(0.05); // encloses the true signal if its error is bounded by 0.05 + // [traj-ops-tube-end] + } + + { + // [traj-ops-serialization-beg] + SampledTraj input({{0.,1}, {1.,1.5}, {2.,2}}); + { + std::ofstream out("trajectory.cdc", std::ios::binary); + serialize(out, input); + } + SampledTraj restored; + { + std::ifstream in("trajectory.cdc", std::ios::binary); + deserialize(in, restored); + } + // [traj-ops-serialization-end] + CHECK(restored.nb_samples() == input.nb_samples()); + } +} + + +TEST_CASE("Trajectory reference - manual") +{ + { + // [traj-sampled-grid-beg] + SampledTraj x({{0.,0}, {1.,2}, {2.,0}}); + auto merged = x.sampled(0.6); // original samples also retained + auto regular = x.sampled(0.6, false); // regular grid and final time only + SampledTraj clock({{0.,0}, {0.5,0}, {1.5,0}, {2.,0}}); + auto aligned = x.sampled_as(clock); // x evaluated at clock's timestamps + // [traj-sampled-grid-end] + CHECK(merged.nb_samples() > regular.nb_samples()); + CHECK(aligned.nb_samples() == clock.nb_samples()); + } + { + // [traj-sampled-construct-alt-beg] + std::map samples({{0.,3}, {1.,4}, {2.,5}}); + SampledTraj x(samples); + x.set(6., 1.); // replaces value at t=1 + SampledTraj y({0.,1.,2.}, {3,6,5}); // two std::list arguments + // [traj-sampled-construct-alt-end] + CHECK(x.nb_samples() == 3); + CHECK(x(1.) == 6.); + CHECK(y(1.) == 6.); + } + { + // [traj-analytic-advanced-beg] + ScalarVar t; + AnalyticTraj a({0,2}, AnalyticFunction({t}, sqr(t))); + auto f = a.as_function(); + AnalyticFunction g({t}, f(t)+1.); + // [traj-analytic-advanced-end] + + } + { + // [traj-ops-pointwise-beg] + SampledTraj x({{0.,0}, {1.,1}, {2.,2}}); + SampledTraj y({{0.,2}, {1.,3}, {2.,4}}); + auto sum = x + y; // computed at matching timestamps + auto sq = sqr(x); // square samples, then interpolate + auto sin_x = sin(x); // transform samples, then interpolate + // [traj-ops-pointwise-end] + CHECK(sum(1.) == 4.); + CHECK(sq(1.) == 1.); + } + { + // [traj-ops-fun-vector-beg] + SampledTraj p({{0.,{3.,4}}, {1.,{0.,5}}}); + VectorVar p_var(2); + AnalyticFunction f({p_var}, sqr(p_var[0]) + sqr(p_var[1])); + auto result = f.traj_eval(p); + // [traj-ops-fun-vector-end] + CHECK(result.size() == 1); + CHECK(result(0.) == 25.); + } + { + // [traj-tubes-mid-beg] + auto td = create_tdomain({0,2}, 0.5, true); + ScalarVar t; + AnalyticTraj a({0,2}, AnalyticFunction({t}, sin(t))); + SlicedTube tube(td,a); + auto midpoint = tube.mid(); // approximate midpoint trajectory + // [traj-tubes-mid-end] + CHECK(midpoint.nb_samples() > 1); + } + { + // [traj-io-csv-beg] + SampledTraj measurements; + std::ifstream csv(std::filesystem::path(__FILE__).parent_path() / "data.csv"); + std::string line; + std::getline(csv, line); // skip header + + while(std::getline(csv, line)) + { + double t, value; + if(std::sscanf(line.c_str(), "%lf;%lf", &t, &value) == 2) + measurements.set(value, t); + } + // [traj-io-csv-end] + CHECK(measurements.nb_samples() == 3); + } +} + +TEST_CASE("SlicedTube cross-reference examples - manual") +{ + { + // [tube-example-1-beg] + auto td = create_tdomain({0,3}, 1., false); + SlicedTube x(td, Interval()); // [-∞,∞] by default for any time + x.set({1,5}, {0,1}); + x.set({2,8}, {1,2}); + x.set({6,9}, {2,3}); + auto y0 = x({0,3}); + auto y1 = x(1.5); + auto y2 = x(1.); + // [tube-example-1-end] + CHECK(!y0.is_empty()); + } + { + // [tube-example-2-beg] + auto td = create_tdomain({0,1}, 0.1, false); + SlicedTube x(td, IntervalVector(3)); + auto before = td->nb_tslices(); + x.set(IntervalVector::constant(3,{2,3}), -42.); + auto after = td->nb_tslices(); // the partition was extended and refined + // [tube-example-2-end] + CHECK(after > before); + } + { + // [tube-example-3-beg] + auto td = create_tdomain({0,5}, 0.1, true); + ScalarVar t; + AnalyticFunction f({t}, 10*cos(t)+t); + SlicedTube x(td, f); // encloses f(t) over each time slice + // [tube-example-3-end] + CHECK(!x.is_empty()); + } + { + // [tube-example-4-beg] + auto td = create_tdomain({6.4,6.5}); + SlicedTube x(td, Interval(0.759,1.205)); + SlicedTube v(td, Interval(-0.216,-0.115)); + x.set(Interval(0.781,1.205), 6.4); + x.set(Interval(0.759,1.194), 6.5); + CtcDeriv ctc; + ctc.contract(x,v); + auto sx = x.first_slice(); + auto sv = v.first_slice(); + auto polygon = sx->polygon_slice(*sv); + // [tube-example-4-end] + } + { + // [tube-example-5-beg] + auto td = create_tdomain({0,2}, 0.5, true); + SlicedTube x(td, Interval(1,2)); + auto integral_at_one = x.integral(Interval(1)); + auto integral_over_range = x.integral(Interval(0.5,1.5)); + auto p = x.primitive(Interval(0)); + // [tube-example-5-end] + CHECK(!integral_at_one.is_empty()); + } + { + // [tube-example-6-beg] + auto td = create_tdomain({0,1}, 0.5, false); + SlicedTube x1(td, Interval(0,1)); + SlicedTube x2(td, IntervalVector({{1,2},{-1,1}})); + auto x = tube_cart_prod(x1,x2); // vector tube of dimension 3 + // [tube-example-6-end] + CHECK(x.size() == 3); + } + { + // [tube-parity-algebra-beg] + auto td = create_tdomain({0,2}, 0.5, true); + SlicedTube x(td, Interval(0,2)); + SlicedTube y(td, Interval(1,3)); + auto both = x & y; // intersection of enclosures + auto either = x | y; // interval hull, not disconnected union + auto sum = x + y; // interval arithmetic on every slice + auto xy = tube_cart_prod(x,y); // vector tube: SlicedTube + // [tube-parity-algebra-end] + CHECK(xy.size() == 2); + } + { + // [tube-parity-conversion-beg] + auto td = create_tdomain({0,2}, 0.5, true); + SlicedTube tube(td, Interval(-1,1)); + SampledTraj midpoint = tube.mid(); // an approximation + SlicedTube middle_curve(td, midpoint); // encloses the interpolant + // [tube-parity-conversion-end] + CHECK(midpoint.nb_samples() > 1); + } + { + // [tube-parity-input-beg] + SampledTraj measured({{0.,1}, {1.,1.5}, {2.,2}}); + auto td = create_tdomain({0,2}, 0.5, true); + SlicedTube x(td, measured); // encloses the piecewise-linear interpolant + x.inflate(0.2); // only guaranteed if the bound is justified + // [tube-parity-input-end] + CHECK(!x.is_empty()); + } + { + // [tube-parity-export-beg] + auto td = create_tdomain({0,2}, 1., true); + SlicedTube x(td, Interval(-1,1)); + std::ostringstream csv; + csv << "t0;t1;lb;ub;is_gate\n"; + for(const auto& s : x) + csv << s.t0_tf().lb() << ';' << s.t0_tf().ub() << ';' + << s.codomain().lb() << ';' << s.codomain().ub() << ';' + << (s.is_gate() ? 1 : 0) << '\n'; + // [tube-parity-export-end] + CHECK(csv.str().find("is_gate") != std::string::npos); + } +} + +TEST_CASE("Slice reference - manual") +{ + { + // [slice-endpoint-example-beg] + auto td = create_tdomain({0,2}, 1., false); + SlicedTube x(td, Interval()); + x.set({1,5}, {0,1}); + x.set({2,8}, {1,2}); + auto s = x.first_slice(); + auto boundary = s->output_gate(); // [2,5] + // [slice-endpoint-example-end] + } + { + // [slice-invert-example-beg] + auto td = create_tdomain({0,1}); + SlicedTube x(td, Interval(0,10)); + x.set({2,3}, 0.); + x.set({5,6}, 1.); + auto s = x.first_slice(); + auto possible_times = s->invert(Interval(2.5,6), Interval(0.2,0.5)); + // [slice-invert-example-end] + } +} diff --git a/doc/manual/manual/tubes/src.py b/doc/manual/manual/tubes/src.py index 6d845dff8..1ab733ca3 100644 --- a/doc/manual/manual/tubes/src.py +++ b/doc/manual/manual/tubes/src.py @@ -30,11 +30,11 @@ def tests_TDomain_manual(test): n = td.nb_tslices() # 5 m = td.nb_tubes() # 0 initially - td.tslice(0.0) # [0] - td.tslice(0.1) # [0,0.5] - td.tslice(0.5) # [0.5] - td.tslice(0.6) # [0.5,1] - td.tslice(1.0) # [1] + td.tslice(0.) # [0] + td.tslice(0.1) # [0,0.5] + td.tslice(0.5) # [0.5] + td.tslice(0.6) # [0.5,1] + td.tslice(1.) # [1] # [tdomain-class-2-end] test.assertTrue(n == 5) @@ -43,32 +43,32 @@ def tests_TDomain_manual(test): # [tdomain-class-3-beg] td = create_tdomain() - td.sample(1.0, False) # [-oo,1],[1,oo] - td.sample(10.0, True) # [-oo,1],[1,10],[10],[10,oo] + td.sample(1., False) # [-oo,1],[1,oo] + td.sample(10., True) # [-oo,1],[1,10],[10],[10,oo] # [tdomain-class-3-end] # [tdomain-class-4-beg] - td = create_tdomain([0,2], 1.0, False) # False: without gates + td = create_tdomain([0,2], 1., False) # False: without gates x = SlicedTube(td, Interval(0,1)) v = SlicedTube(td, Interval(-1,1)) - print(x) # outputs [0,2]->[0,1], 2 slices - print(v) # outputs [0,2]->[0,1], 2 slices + print(x) # codomain [0,1], 2 temporal elements + print(v) # codomain [-1,1], 2 temporal elements n = td.nb_tslices() # 2: [0,1],[1,2] x.set([0.5,1], 1.3) # local update, will refine the partition at t=1.3 m = td.nb_tslices() # now 4: [0,1],[1,1.3],[1.3],[1.3,2] - print(x) # outputs [0,2]->[-1,1], 4 slices - print(v) # outputs [0,2]->[-1,1], 4 slices (v is also impacted by x.set(..)) + print(x) # codomain [0,1], 4 temporal elements + print(v) # codomain [-1,1], 4 temporal elements (shared partition refined) # [tdomain-class-4-end] test.assertTrue(n == 2) test.assertTrue(m == 4) # [tdomain-class-5-beg] - td = create_tdomain([0,3], 1.0, False) + td = create_tdomain([0,3], 1., False) x = SlicedTube(td, Interval(0,1)) td.nb_tubes() # 1 @@ -88,14 +88,14 @@ def tests_SlicedTube_manual(test): td = create_tdomain([0,10], 0.1, True) # Sliced tube from interval-type codomains - x = SlicedTube(td, Interval(-1,1)) - y = SlicedTube(td, IntervalVector(2)) - z = SlicedTube(td, IntervalMatrix(2,2)) + x = SlicedTube(td, Interval(-1,1)) # scalar tube + y = SlicedTube(td, IntervalVector(2)) # vector tube + z = SlicedTube(td, IntervalMatrix(2,2)) # matrix tube # From an analytic function t = ScalarVar() f = AnalyticFunction([t], sin(t)) - xf = SlicedTube(td, f) + xf = SlicedTube(td, f) # scalar tube # From an analytic trajectory or sampled trajectory at = AnalyticTraj([0,10], AnalyticFunction([t],cos(t))) @@ -121,7 +121,7 @@ def tests_SlicedTube_manual(test): # [slicedtube-class-2-end] # [slicedtube-class-4-beg] - td = create_tdomain([0,3], 1.0, False) + td = create_tdomain([0,3], 1., False) x = SlicedTube(td, Interval()) x.set([1,5], [0,1]) @@ -131,12 +131,12 @@ def tests_SlicedTube_manual(test): x(0.5) # [1,5] x(1.5) # [2,8] x([0,3]) # [1,9] - x(-1.0) # [-oo,oo] + x(-1.) # [-oo,oo] # No explicit gates: boundary values come from adjacent-slice intersections - x(1.0) # [2,5] - x(2.0) # [6,8] - x(3.0) # [6,9] + x(1.) # [2,5] + x(2.) # [6,8] + x(3.) # [6,9] # [slicedtube-class-4-end] T = Interval(0,10) @@ -167,7 +167,7 @@ def tests_SlicedTube_manual(test): # [slicedtube-class-5-end] # [slicedtube-class-6-beg] - td = create_tdomain([0,2], 1.0, False) + td = create_tdomain([0,2], 1., False) x = SlicedTube(td, Interval(1,2)) y = SlicedTube(td, Interval(-1,3)) @@ -184,7 +184,7 @@ def tests_SlicedTube_manual(test): A = SlicedTube(td, IntervalMatrix(2,2)) b = SlicedTube(td, IntervalVector(2)) - y = A * b + y = A * b # vector tube # [slicedtube-class-7-end] # [slicedtube-class-function-eval-beg] @@ -196,7 +196,7 @@ def tests_SlicedTube_manual(test): h, v = ScalarVar(), ScalarVar() f = AnalyticFunction([h,v], [v*cos(h), v*sin(h)]) - velocity = f.tube_eval(heading,speed) # vector-valued tube + velocity = f.tube_eval(heading,speed) # vector tube p = VectorVar(2) g = AnalyticFunction([p], sqr(p[0])+sqr(p[1])) @@ -212,7 +212,7 @@ def tests_SlicedTube_manual(test): # [slicedtube-class-8-beg] x.inflate(0.2) # constant inflation - rad = SampledTraj({0.0:0.1, 1.0:0.3, 2.0:0.2}) + rad = SampledTraj({0.:0.1, 1.:0.3, 2.:0.2}) x.inflate(rad) # time-varying inflation radius # [slicedtube-class-8-end] @@ -221,11 +221,326 @@ def tests_SlicedTube_manual(test): # [slicedtube-class-9-beg] # Component and subvector extraction - x0 = x[0] - x12 = x.subvector(1,2) + x0 = x[0] # scalar tube + x12 = x.subvector(1,2) # vector tube # [slicedtube-class-9-end] pass + + def test_Trajectories_manual(test): + # [traj-sampled-create-beg] + depth = SampledTraj_Scalar() + depth.set(2., 0.) + depth.set(3., 1.) + depth.set(4., 2.) + + position = SampledTraj_Vector() + position.set(Vector([0,1]), 0.) + position.set([1,2], 1.) # equivalent shorter notation + position.set([2,3], 2.) + # [traj-sampled-create-end] + test.assertEqual(depth.nb_samples(), 3) + + # [traj-sampled-eval-beg] + x = SampledTraj({0.: 2., 1.: 4., 2.: 3.}) + value = x(0.5) # 3. + range_x = x([0.5,1.5]) # [3,4] + sample_count = x.nb_samples() # 3 + output_size = x.size() # 1 + # [traj-sampled-eval-end] + test.assertEqual(value, 3) + test.assertEqual(range_x, Interval(3,4)) + test.assertEqual(sample_count, 3) + test.assertEqual(output_size, 1) + + # [traj-sampled-iterate-beg] + for ti, xi in x: + print(ti, xi) + # [traj-sampled-iterate-end] + test.assertAlmostEqual(value, 3.) + + # [traj-sampled-components-beg] + p = SampledTraj({0.: Vector([0.,1.,2.]), + 1.: Vector([1.,2.,3.])}) + x = p[0] + yz = p.subvector(1,2) + # [traj-sampled-components-end] + + # [traj-sampled-time-beg] + shifted = SampledTraj({0.:0., 1.:1., 2.:0.}) + shifted.truncate_tdomain([0.5,1.5]) # retains interpolated endpoints + shifted.shift_tdomain(10.) # time domain now [10.5,11.5] + shifted.stretch_tdomain([0,1]) # rescale time, not values + # [traj-sampled-time-end] + + # [traj-sampled-npz-beg] + import numpy as np + import io + memory = io.BytesIO() + np.savez(memory, + t=np.array([0., 0.5, 1.]), + x=np.array([[0,1], [0.5,1.5], [1,2]])) + memory.seek(0) # rewind before reading + with np.load(memory) as data: + imported = SampledTraj(data['t'], data['x']) + # imported is a SampledTraj_Vector + # [traj-sampled-npz-end] + + # [traj-analytic-create-beg] + t = ScalarVar() + f = AnalyticFunction([t], 2*sin(t)) + traj_f = AnalyticTraj([0,2], f) # scalar trajectory + + g = AnalyticFunction([t], [cos(t), sin(t)]) + traj_g = AnalyticTraj([0,2], g) # vector trajectory + # [traj-analytic-create-end] + + # [traj-analytic-eval-beg] + y_point = traj_f(0.5) # scalar value + y_interval = traj_f([0,1]) # interval enclosure + p_point = traj_g(0.5) # vector value + # [traj-analytic-eval-end] + + # [traj-analytic-sampled-beg] + f_samples = traj_f.sampled(0.1) + g_samples = traj_g.sampled(0.1) + # [traj-analytic-sampled-end] + + # [traj-ops-calculus-beg] + speed = SampledTraj({0.:0, 1.:1, 2.:2}) + acceleration = speed.derivative() # numerical derivative + distance = speed.primitive() # trapezoidal integral, p(0)=0 + doubled_speed = 2. * speed # pointwise arithmetic + # [traj-ops-calculus-end] + + # [traj-ops-cartprod-beg] + x1 = SampledTraj({0.:0, 1.:1}) + x2 = SampledTraj({0.:2, 1.:3}) + x = traj_cart_prod(x1, x2) + # [traj-ops-cartprod-end] + + # [traj-ops-continuous-beg] + heading = SampledTraj({0.:6.1, 1.:0.1, 2.:0.2}) + continuous_heading = continuous_traj(heading) + # [traj-ops-continuous-end] + + # [traj-ops-eval-beg] + theta = SampledTraj({0.:0, 1.:0.1, 2.:0.2}) + v = SampledTraj({0.:1., 1.:2., 2.:3.}) + theta_var, v_var = ScalarVar(), ScalarVar() + f = AnalyticFunction([theta_var, v_var], [v_var*cos(theta_var), v_var*sin(theta_var)]) + u = f.traj_eval(theta, v) + # [traj-ops-eval-end] + + # [traj-ops-asfunction-beg] + measurements = SampledTraj({0.:1, 1.:2, 2.:1.5}) + measurement_fn = measurements.as_function() + t = ScalarVar() + energy = AnalyticFunction([t], sqr(measurement_fn(t))) + estimated_energy = energy.eval(0.5) + # [traj-ops-asfunction-end] + + # [traj-ops-tube-beg] + t = ScalarVar() + traj = AnalyticTraj([0,2], AnalyticFunction([t],sin(t))) + td = create_tdomain([0,2], 0.1) + tube = SlicedTube(td, traj) + tube.inflate(0.05) # encloses the true signal if its error is bounded by 0.05 + # [traj-ops-tube-end] + + # [traj-ops-serialization-beg] + input_traj = SampledTraj({0.:1, 1.:1.5, 2.:2}) + with open('trajectory_python.cdc', 'wb') as f: + serialize(f, input_traj) + restored = SampledTraj_Scalar() + with open('trajectory_python.cdc', 'rb') as f: + deserialize(f, restored) + # [traj-ops-serialization-end] + test.assertEqual(restored.nb_samples(), input_traj.nb_samples()) + + + def test_Trajectory_reference_manual(test): + # [traj-sampled-grid-beg] + x = SampledTraj_Scalar({0.:0, 1.:2, 2.:0}) + merged = x.sampled(0.6) # original samples also retained + regular = x.sampled(0.6, False) # regular grid and final time only + clock = SampledTraj_Scalar({0.:0, 0.5:0, 1.5:0, 2.:0}) + aligned = x.sampled_as(clock, False) # x evaluated at clock's timestamps + # [traj-sampled-grid-end] + test.assertGreater(merged.nb_samples(), regular.nb_samples()) + test.assertEqual(aligned.nb_samples(),clock.nb_samples()) + + # [traj-sampled-construct-alt-beg] + x = SampledTraj_Scalar([0.,1.,2.], [3,4,5]) + x.set(6.,1.) # replaces value at t=1 + y = SampledTraj_Scalar({0.:3, 1.:6, 2.:5}) + # [traj-sampled-construct-alt-end] + test.assertEqual(x.nb_samples(), 3) + test.assertAlmostEqual(x(1.),6.) + + # [traj-analytic-advanced-beg] + t = ScalarVar() + a = AnalyticTraj([0,2], AnalyticFunction([t], sqr(t))) + f = a.as_function() + g = AnalyticFunction([t], f(t) + 1) + # [traj-analytic-advanced-end] + + # [traj-ops-pointwise-beg] + x = SampledTraj_Scalar({0.:0, 1.:1, 2.:2}) + y = SampledTraj_Scalar({0.:2, 1.:3, 2.:4}) + sum_xy = x + y # computed at matching timestamps + sq = sqr(x) # square samples, then interpolate + sin_x = sin(x) # transform samples, then interpolate + # [traj-ops-pointwise-end] + test.assertAlmostEqual(sum_xy(1),4.) + + # [traj-ops-fun-vector-beg] + p = SampledTraj({0.: Vector([3,4]), 1.: Vector([0,5])}) + p_var = VectorVar(2) + f = AnalyticFunction([p_var], sqr(p_var[0]) + sqr(p_var[1])) + result = f.traj_eval(p) + # [traj-ops-fun-vector-end] + test.assertAlmostEqual(result(0),25.) + + # [traj-tubes-mid-beg] + td = create_tdomain([0,2],0.5,True) + t = ScalarVar() + a = AnalyticTraj([0,2],AnalyticFunction([t],sin(t))) + tube = SlicedTube(td,a) + midpoint = tube.mid() # approximate midpoint trajectory + # [traj-tubes-mid-end] + test.assertGreater(midpoint.nb_samples(),1) + + # [traj-io-csv-beg] + import csv + from pathlib import Path + measurements = SampledTraj_Scalar() + with (Path(__file__).parent / 'data.csv').open(newline='') as file: + # The header is parsed automatically. + for row in csv.DictReader(file, delimiter=';'): + measurements.set(float(row['value']), float(row['t'])) + # [traj-io-csv-end] + test.assertEqual(measurements.nb_samples(),3) + + def test_SlicedTube_cross_reference_manual(test): + # [tube-example-1-beg] + td = create_tdomain([0,3], 1., False) + x = SlicedTube(td, Interval()) # [-∞,∞] by default for any time + x.set([1,5], [0,1]) + x.set([2,8], [1,2]) + x.set([6,9], [2,3]) + y0 = x([0,3]) + y1 = x(1.5) + y2 = x(1.) + # [tube-example-1-end] + test.assertFalse(y0.is_empty()) + + # [tube-example-2-beg] + td = create_tdomain([0,1], 0.1, False) + x = SlicedTube(td, IntervalVector(3)) + before = td.nb_tslices() + x.set(IntervalVector.constant(3,[2,3]), -42.) + after = td.nb_tslices() # the partition was extended and refined + # [tube-example-2-end] + test.assertGreater(after, before) + + # [tube-example-3-beg] + td = create_tdomain([0,5], 0.1, True) + t = ScalarVar() + f = AnalyticFunction([t], 10*cos(t) + t) + x = SlicedTube(td, f) # encloses f(t) over each time slice + # [tube-example-3-end] + test.assertFalse(x.is_empty()) + + # [tube-example-4-beg] + td = create_tdomain([6.4,6.5]) + x = SlicedTube(td, Interval(0.759,1.205)) + v = SlicedTube(td, Interval(-0.216,-0.115)) + x.set(Interval(0.781,1.205), 6.4) + x.set(Interval(0.759,1.194), 6.5) + CtcDeriv().contract(x,v) + sx = x.first_slice() + sv = v.first_slice() + polygon = sx.polygon_slice(sv) + # [tube-example-4-end] + + # [tube-example-5-beg] + td = create_tdomain([0,2], 0.5, True) + x = SlicedTube(td, Interval(1,2)) + integral_at_one = x.integral(Interval(1)) + integral_over_range = x.integral(Interval(0.5,1.5)) + p = x.primitive(Interval(0)) + # [tube-example-5-end] + test.assertFalse(integral_at_one.is_empty()) + + # [tube-example-6-beg] + td = create_tdomain([0,1], 0.5, False) + x1 = SlicedTube(td, Interval(0,1)) + x2 = SlicedTube(td, IntervalVector([[1,2],[-1,1]])) + x = tube_cart_prod(x1, x2) # vector tube of dimension 3 + # [tube-example-6-end] + test.assertEqual(x.size(), 3) + + # [tube-parity-algebra-beg] + td = create_tdomain([0,2], 0.5, True) + x = SlicedTube(td, Interval(0,2)) + y = SlicedTube(td, Interval(1,3)) + both = x & y # intersection of enclosures + either = x | y # interval hull, not disconnected union + sum_xy = x + y # interval arithmetic on every slice + xy = tube_cart_prod(x,y) # vector tube: SlicedTube_IntervalVector + # [tube-parity-algebra-end] + test.assertEqual(xy.size(), 2) + + # [tube-parity-conversion-beg] + td = create_tdomain([0,2], 0.5, True) + tube = SlicedTube(td, Interval(-1,1)) + midpoint = tube.mid() # an approximation + middle_curve = SlicedTube(td, midpoint) # encloses the interpolant + # [tube-parity-conversion-end] + test.assertGreater(midpoint.nb_samples(), 1) + + # [tube-parity-input-beg] + measured = SampledTraj_Scalar({0.:1., 1.:1.5, 2.:2.}) + td = create_tdomain([0,2], 0.5, True) + x = SlicedTube(td, measured) # encloses the piecewise-linear interpolant + x.inflate(0.2) # only guaranteed if the bound is justified + # [tube-parity-input-end] + test.assertFalse(x.is_empty()) + + # [tube-parity-export-beg] + import io + td = create_tdomain([0,2], 1., True) + x = SlicedTube(td, Interval(-1,1)) + csv = io.StringIO() + csv.write('t0;t1;lb;ub;is_gate\n') + for s in x: + t = s.t0_tf() + bounds = s.codomain() + csv.write(f'{t.lb()};{t.ub()};{bounds.lb()};{bounds.ub()};{int(s.is_gate())}\n') + # [tube-parity-export-end] + test.assertIn('is_gate', csv.getvalue()) + + + def test_Slice_reference_manual(test): + # [slice-endpoint-example-beg] + td = create_tdomain([0,2], 1., False) + x = SlicedTube(td, Interval()) + x.set([1,5], [0,1]) + x.set([2,8], [1,2]) + s = x.first_slice() + boundary = s.output_gate() # [2,5] + # [slice-endpoint-example-end] + + # [slice-invert-example-beg] + td = create_tdomain([0,1]) + x = SlicedTube(td, Interval(0,10)) + x.set([2,3], 0) + x.set([5,6], 1) + s = x.first_slice() + possible_times = s.invert([2.5,6], [0.2,0.5]) + # [slice-invert-example-end] + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/doc/manual/manual/tubes/tdomain.rst b/doc/manual/manual/tubes/tdomain.rst index d65eebdc4..27541aba0 100644 --- a/doc/manual/manual/tubes/tdomain.rst +++ b/doc/manual/manual/tubes/tdomain.rst @@ -1,19 +1,16 @@ -.. _sec-domains-tubes-tdomain: +.. _sec-tubes-tdomain: The TDomain class ================= Main author: `Simon Rohou `_ -A :class:`~codac.TDomain` is the temporal partition shared by one or more sliced tubes. -It stores an ordered list of :class:`~codac.TSlice` objects. Each stored element is either +A :class:`~codac.TDomain` is the temporal partition shared by one or more sliced tubes. It stores an ordered list of :class:`~codac.TSlice` objects. Each stored element is either * a non-degenerate temporal slice :math:`[t_i,t_{i+1}]`, or * a degenerate temporal slice :math:`[t_i,t_i]`, called a *gate*. -Gates are optional. When they are explicitly represented, the partition may alternate between -``gate / slice / gate / slice / ...``. This is the structure created by -``create_tdomain(t0_tf, dt, True)``. +Gates are optional. When they are explicitly represented, the partition may alternate between ``gate / slice / gate / slice / ...``. This is the structure created by ``create_tdomain(t0_tf, dt, True)``. Although optional, gates are useful when one needs to represent tube values explicitly at specific time instants. This is particularly relevant for guaranteed integration. @@ -77,8 +74,7 @@ A temporal domain exposes: :dedent: 4 -Note that ``tslice(t)`` returns the gate when ``t`` matches an explicit gate, and -otherwise returns the unique non-degenerate slice containing ``t``. +Note that ``tslice(t)`` returns the gate when ``t`` matches an explicit gate, and otherwise returns the unique non-degenerate slice containing ``t``. Sampling and refinement ----------------------- @@ -112,17 +108,12 @@ Sampling can occur: :end-before: [tdomain-class-3-end] :dedent: 4 -One important implementation detail is that refinement preserves the attached tubes by cloning the -corresponding slice objects onto the new temporal elements. The new slices are then reattached to -the newly inserted ``TSlice`` iterators. +One important implementation detail is that refinement preserves the attached tubes by cloning the corresponding slice objects onto the new temporal elements. The new slices are then reattached to the newly inserted ``TSlice`` iterators. Shared refinement across several tubes -------------------------------------- -One practical consequence of the shared-``TDomain`` design is that refining the time -partition from one tube automatically updates the slice structure seen by all the -other tubes attached to the same temporal domain. -The following code illustrates this effect: +One practical consequence of the shared-``TDomain`` design is that refining the time partition from one tube automatically updates the slice structure seen by all the other tubes attached to the same temporal domain. The following code illustrates this effect: .. tabs:: @@ -144,31 +135,22 @@ The following code illustrates this effect: .. note:: - This behavior is intentional: it keeps all related tubes on a consistent shared - temporal support, which greatly simplifies multi-tube operations such as - arithmetic combinations and contractions. + A shared temporal domain keeps related tubes consistent when the partition is refined, which simplifies multi-tube operations and contractions. Gates ----- -Explicit gates are useful when you want to represent endpoint values separately from the interior -codomain of a slice. The current API provides: +Explicit gates are useful when you want to represent endpoint values separately from the interior codomain of a slice. The current API provides: * ``all_gates_defined()`` to detect a fully gate-augmented partition, * ``delete_gates()`` to remove all explicit gates. -Therefore, a :math:`t`-domain built from ``create_tdomain([0,1], 0.5, True)`` contains five temporal elements and is a -typical example of a gate-augmented partition. -In this implementation, a gate is not a separate data type: it is simply a degenerate -:class:`~codac.TSlice`, i.e., an interval :math:`[t,t]`. +Therefore, a :math:`t`-domain built from ``create_tdomain([0,1], 0.5, True)`` contains five temporal elements and is a typical example of a gate-augmented partition. In this implementation, a gate is not a separate data type: it is simply a degenerate :class:`~codac.TSlice`, i.e., an interval :math:`[t,t]`. Shared temporal structure ------------------------- -A :class:`~codac.TDomain` is shared by all sliced tubes built from it. -As a consequence, structural operations such as ``truncate(...)`` affect all -attached tubes, and the number of attached tubes can be queried with -``nb_tubes()``. +A :class:`~codac.TDomain` is shared by all sliced tubes built from it. As a consequence, structural operations such as ``truncate(...)`` affect all attached tubes, and the number of attached tubes can be queried with ``nb_tubes()``. .. tabs:: @@ -186,4 +168,4 @@ attached tubes, and the number of attached tubes can be queried with :language: c++ :start-after: [tdomain-class-5-beg] :end-before: [tdomain-class-5-end] - :dedent: 4 \ No newline at end of file + :dedent: 4 diff --git a/doc/manual/manual/tubes/trajectory_io.rst b/doc/manual/manual/tubes/trajectory_io.rst new file mode 100644 index 000000000..dad9c2731 --- /dev/null +++ b/doc/manual/manual/tubes/trajectory_io.rst @@ -0,0 +1,102 @@ +.. _sec-trajectories-io: + +Trajectories serialization +========================== + + Main author: `Simon Rohou `_ + +Importing CSV data +------------------ + +A ``SampledTraj`` can be filled from the following ``data.csv`` file: + +.. literalinclude:: data.csv + :language: text + +Read its time/value columns and insert the samples using ``set(value,time)``. The examples locate ``data.csv`` alongside the source file. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-io-csv-beg] + :end-before: [traj-io-csv-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-io-csv-beg] + :end-before: [traj-io-csv-end] + :dedent: 4 + +NumPy archives +-------------- + +In Python, load a ``.npz`` file with ``numpy.load`` and pass its time and value arrays to ``SampledTraj``. The trajectory type is inferred from the values. + +.. code-block:: python + + import numpy as np + from codac import SampledTraj + + data = np.load("traj_vec.npz") + traj = SampledTraj(data["t"], data["x"]) + +In the above example, if ``x`` is a 2D array of shape ``(N,n)``, then ``traj`` is a ``SampledTraj_Vector``. + +Another example: + +.. tabs:: + + .. group-tab:: Python + + .. code-block:: python + + import numpy as np + from codac import * + + t = np.array([0.0, 0.5, 1.0, 1.5]) + x = np.array([ + [0.0, 1.0], + [0.5, 1.5], + [1.0, 2.0], + [1.5, 2.5], + ]) + + traj = SampledTraj(t, x) + print(traj) # outputs: SampledTraj. [0, 1.5]↦[[0, 1.5][1, 2.5]], 4 pts + + +Binary serialization +-------------------- + +``serialize`` and ``deserialize`` save and restore ``SampledTraj`` objects in binary form. The ``.cdc`` extension is conventional. An ``AnalyticTraj`` can be sampled before saving, but the symbolic expression itself is not serialized. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-serialization-beg] + :end-before: [traj-ops-serialization-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-serialization-beg] + :end-before: [traj-ops-serialization-end] + :dedent: 4 + +See :ref:`sec-tools-serialization` for details about the binary format. + +Visualization +------------- + +Use ``Figure2D.plot_trajectory`` for scalar time series and ``Figure2D.plot_trajectories`` for vector time series. Spatial trajectories can be displayed with ``DefaultFigure.draw_trajectory``. See :doc:`../visualization/functions`. diff --git a/doc/manual/manual/tubes/trajectory_operations.rst b/doc/manual/manual/tubes/trajectory_operations.rst new file mode 100644 index 000000000..72302c2ad --- /dev/null +++ b/doc/manual/manual/tubes/trajectory_operations.rst @@ -0,0 +1,190 @@ +.. _sec-trajectories-operations: + +Operations on trajectories +========================== + + Main author: `Simon Rohou `_ + +Arithmetic and calculus +----------------------- + +Operations such as ``x1+x2`` or ``sin(x1)`` compute values at the stored timestamps. The resulting ``SampledTraj`` interpolates linearly between these values. Arithmetic between two sampled trajectories requires identical timestamps. + +For a scalar or vector trajectory :math:`x`, ``primitive(dt)`` approximates the integral + +.. math:: + + p(t)=\int_{t_0}^{t}x(\tau)\,d\tau,\qquad p(t_0)=0, + +using the trapezoidal rule, with integration step ``dt``. For a ``SampledTraj``, ``primitive()`` instead integrates over its existing sample intervals: + +.. math:: + + p(t_{i+1})=p(t_i)+\frac{t_{i+1}-t_i}{2}(x_i+x_{i+1}). + +``derivative()`` approximates the derivative of a sampled trajectory by finite differences. These numerical operations do not provide guaranteed bounds. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-calculus-beg] + :end-before: [traj-ops-calculus-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-calculus-beg] + :end-before: [traj-ops-calculus-end] + :dedent: 4 + + + +Cartesian products +------------------ + +``traj_cart_prod(x1,x2,...)`` combines scalar or vector sampled trajectories into a vector trajectory. The timestamps must be identical. Use ``sampled_as()`` to align trajectories when necessary. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-cartprod-beg] + :end-before: [traj-ops-cartprod-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-cartprod-beg] + :end-before: [traj-ops-cartprod-end] + :dedent: 4 + +Unwrapping periodic signals +--------------------------- + +``continuous_traj()`` removes apparent jumps in a sampled scalar signal, for instance a heading crossing its angular discontinuity. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-continuous-beg] + :end-before: [traj-ops-continuous-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-continuous-beg] + :end-before: [traj-ops-continuous-end] + :dedent: 4 + +The function detects jumps between consecutive samples and corrects them using a period estimated from the observed values. This is particularly useful for trajectories representing angles. Since the period is estimated rather than specified, the correction may be approximate. + +Applying analytic functions +--------------------------- + +``f.traj_eval(x1,x2,...)`` evaluates an ``AnalyticFunction`` on sampled trajectories at the timestamps of the first input, interpolating the other inputs as needed. In Python, inputs are all scalar trajectories or all vector trajectories, while C++ also accepts mixed inputs. For interval enclosures on tubes, see :ref:`sec-domains-tubes-slicedtube-function-eval`. + +For example, given a heading :math:`\theta(t)` and a speed :math:`v(t)`, an analytic function computes the Cartesian velocity components: + +.. math:: + + \mathbf{u}(t)=\begin{pmatrix}v(t)\cos\theta(t)\\v(t)\sin\theta(t)\end{pmatrix}. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-eval-beg] + :end-before: [traj-ops-eval-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-eval-beg] + :end-before: [traj-ops-eval-end] + :dedent: 4 + +For a vector-valued input trajectory, use a ``VectorVar``: + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-fun-vector-beg] + :end-before: [traj-ops-fun-vector-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-fun-vector-beg] + :end-before: [traj-ops-fun-vector-end] + :dedent: 4 + +Using a trajectory in an analytic expression +-------------------------------------------- + +``as_function()`` makes a trajectory available inside an analytic expression of time. This is useful when an expression depends on an existing time series. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-asfunction-beg] + :end-before: [traj-ops-asfunction-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-asfunction-beg] + :end-before: [traj-ops-asfunction-end] + :dedent: 4 + +The trajectory operator currently supports forward evaluation but not general backward contraction or derivative propagation through the expression. + +Converting trajectories and tubes +--------------------------------- + +``SlicedTube(td,traj)`` encloses a trajectory on a temporal partition. To enclose an unknown signal, additional measurement and interpolation uncertainty may be needed. Conversely, ``tube.mid()`` gives a numerical midpoint trajectory without retaining the tube's uncertainty. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [traj-ops-tube-beg] + :end-before: [traj-ops-tube-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [traj-ops-tube-beg] + :end-before: [traj-ops-tube-end] + :dedent: 4 diff --git a/doc/manual/manual/tubes/tube_examples.rst b/doc/manual/manual/tubes/tube_examples.rst new file mode 100644 index 000000000..62d173e01 --- /dev/null +++ b/doc/manual/manual/tubes/tube_examples.rst @@ -0,0 +1,159 @@ +.. _sec-tubes-examples: + +Programming examples +==================== + + Main author: `Simon Rohou `_ + +The following examples illustrate basic operations on sliced tubes. + +Example 1: building and evaluating a scalar tube +------------------------------------------------ + +The following example builds a scalar tube over a sampled temporal domain and assigns different values on successive temporal intervals. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-1-beg] + :end-before: [tube-example-1-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-1-beg] + :end-before: [tube-example-1-end] + :dedent: 4 + + + +Example 2: refining a temporal domain from a tube update +-------------------------------------------------------- + +Local assignments refine the shared temporal partition when necessary. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-2-beg] + :end-before: [tube-example-2-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-2-beg] + :end-before: [tube-example-2-end] + :dedent: 4 + +The same partition is updated for all tubes sharing the temporal domain. + +Example 3: tube from an analytic function +----------------------------------------- + +For :math:`f(t)=10\cos(t)+t`, a ``SlicedTube`` encloses the function over each time slice rather than evaluating it only at isolated timestamps. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-3-beg] + :end-before: [tube-example-3-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-3-beg] + :end-before: [tube-example-3-end] + :dedent: 4 + + +Example 4: derivative-aware slice reasoning +------------------------------------------- + +The following pattern uses a derivative tube and ``CtcDeriv`` to sharpen a scalar slice. The slice can then build a polygonal enclosure used for refined evaluation or inversion. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-4-beg] + :end-before: [tube-example-4-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-4-beg] + :end-before: [tube-example-4-end] + :dedent: 4 + +The derivative constraint can narrow the tube before evaluating individual slices. + +Example 5: integration and primitive tubes +------------------------------------------ + +The integration API supports point and interval queries and can also build a primitive tube. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-5-beg] + :end-before: [tube-example-5-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-5-beg] + :end-before: [tube-example-5-end] + :dedent: 4 + + + +Example 6: Cartesian product of tubes +------------------------------------- + +When several sliced tubes share the same temporal domain, you can build a vector-valued tube by stacking their codomains slice by slice. + +.. tabs:: + + .. group-tab:: Python + + .. literalinclude:: src.py + :language: python + :start-after: [tube-example-6-beg] + :end-before: [tube-example-6-end] + :dedent: 4 + + .. group-tab:: C++ + + .. literalinclude:: src.cpp + :language: c++ + :start-after: [tube-example-6-beg] + :end-before: [tube-example-6-end] + :dedent: 4 + +All inputs must share the same temporal-domain object. + +For the analogous operation on ``SampledTraj`` objects, see :ref:`sec-trajectories-operations` (``traj_cart_prod``). diff --git a/doc/manual/manual/visualization/colors.rst b/doc/manual/manual/visualization/colors.rst index aaba29598..bf36d6826 100644 --- a/doc/manual/manual/visualization/colors.rst +++ b/doc/manual/manual/visualization/colors.rst @@ -129,6 +129,7 @@ Additionnal methods are available for any useful purpose: .. _subsec-graphics-colors-style-properties: + StyleProperties --------------- @@ -208,7 +209,9 @@ For more information, see :ref:`subsec-graphics-colors-optional-arguments`. fig.draw_box(IntervalVector({{2,5},{2,5}}), StyleProperties(Color().red(), "..", "layer1", "w:0.1", "z:1.5")); % Red edge, dotted line, line width of 0.1, z-value of 1.5 and on layer1 + .. _subsec-graphics-colors-paving-style: + Paving style ------------ @@ -341,7 +344,9 @@ You can also create your own color map : Note that you can add RGB and HSV colors to the same color map. The model of the color map will define the interpolation space. + .. _subsec-graphics-colors-style-gradient-properties: + StyleGradientProperties ----------------------- diff --git a/doc/manual/tuto/cp_robotics/lesson_b_data_association.rst b/doc/manual/tuto/cp_robotics/lesson_b_data_association.rst index 27d59a83f..ed0fb5c52 100644 --- a/doc/manual/tuto/cp_robotics/lesson_b_data_association.rst +++ b/doc/manual/tuto/cp_robotics/lesson_b_data_association.rst @@ -168,7 +168,7 @@ where :math:`\bigsqcup`, called *squared union*, returns the smallest box enclos \mathcal{C}_\mathbb{M}\left(\left[\mathbf{a}\right]\right)=\bigsqcup_{j=1}^{\ell}\big(\left[\mathbf{a}\right]\cap[\mathbf{m}_{j}]\big) - The user manual may help you to :ref:`compute operations on sets ` such as unions or intersections, appearing in the formula. Note that you cannot change the definition of the ``.contract()`` method. + The user manual may help you to :ref:`compute operations on sets ` such as unions or intersections, appearing in the formula. Note that you cannot change the definition of the ``.contract()`` method. .. container:: toggle, toggle-hidden