From 66e260367ac774d563874203b260b0ed0e04c93d Mon Sep 17 00:00:00 2001
From: robot-piglet <robot-piglet@yandex-team.com>
Date: Sat, 14 Sep 2024 10:18:18 +0300
Subject: Intermediate changes
 commit_hash:ab55b3b972e940f79570e58af046841fae66b70c

---
 contrib/python/contourpy/src/base.h                |  16 +-
 contrib/python/contourpy/src/base_impl.h           | 120 ++++++++-----
 contrib/python/contourpy/src/common.h              |   1 +
 contrib/python/contourpy/src/contour_generator.cpp |  79 +++++++++
 contrib/python/contourpy/src/contour_generator.h   |  13 ++
 contrib/python/contourpy/src/converter.cpp         |   8 +-
 contrib/python/contourpy/src/mpl2005.cpp           |   8 +-
 contrib/python/contourpy/src/mpl2005.h             |   6 +-
 contrib/python/contourpy/src/mpl2014.cpp           |   9 +-
 contrib/python/contourpy/src/mpl2014.h             |   6 +-
 contrib/python/contourpy/src/util.cpp              |   6 +
 contrib/python/contourpy/src/util.h                |   2 +
 contrib/python/contourpy/src/wrap.cpp              | 194 ++++++++++-----------
 13 files changed, 300 insertions(+), 168 deletions(-)
 create mode 100644 contrib/python/contourpy/src/contour_generator.cpp

(limited to 'contrib/python/contourpy/src')

diff --git a/contrib/python/contourpy/src/base.h b/contrib/python/contourpy/src/base.h
index e626e99431..6b4d99d17e 100644
--- a/contrib/python/contourpy/src/base.h
+++ b/contrib/python/contourpy/src/base.h
@@ -24,11 +24,13 @@ template <typename Derived>
 class BaseContourGenerator : public ContourGenerator
 {
 public:
-    ~BaseContourGenerator();
+    virtual ~BaseContourGenerator();
 
     static FillType default_fill_type();
     static LineType default_line_type();
 
+    py::tuple filled(double lower_level, double upper_level) override;
+
     py::tuple get_chunk_count() const;  // Return (y_chunk_count, x_chunk_count)
     py::tuple get_chunk_size() const;   // Return (y_chunk_size, x_chunk_size)
 
@@ -41,8 +43,10 @@ public:
 
     ZInterp get_z_interp() const;
 
-    py::sequence filled(double lower_level, double upper_level);
-    py::sequence lines(double level);
+    py::sequence lines(double level) override;
+
+    py::list multi_filled(const LevelArray levels) override;
+    py::list multi_lines(const LevelArray levels) override;
 
     static bool supports_fill_type(FillType fill_type);
     static bool supports_line_type(LineType line_type);
@@ -149,8 +153,6 @@ protected:
     void interp(
         index_t point0, double x1, double y1, double z1, bool is_upper, double*& points) const;
 
-    bool is_filled() const;
-
     bool is_point_in_chunk(index_t point, const ChunkLocal& local) const;
 
     bool is_quad_in_bounds(
@@ -166,6 +168,10 @@ protected:
 
     void move_to_next_boundary_edge(index_t& quad, index_t& forward, index_t& left) const;
 
+    // Common initialisation for filled/multi_filled and lines/multi_lines calls.
+    void pre_filled();
+    void pre_lines();
+
     void set_look_flags(index_t hole_start_quad);
 
     void write_cache_quad(index_t quad) const;
diff --git a/contrib/python/contourpy/src/base_impl.h b/contrib/python/contourpy/src/base_impl.h
index 6175ad5c56..80c9e51854 100644
--- a/contrib/python/contourpy/src/base_impl.h
+++ b/contrib/python/contourpy/src/base_impl.h
@@ -351,28 +351,13 @@ LineType BaseContourGenerator<Derived>::default_line_type()
 }
 
 template <typename Derived>
-py::sequence BaseContourGenerator<Derived>::filled(double lower_level, double upper_level)
+py::tuple BaseContourGenerator<Derived>::filled(double lower_level, double upper_level)
 {
-    if (lower_level >= upper_level)
-        throw std::invalid_argument("upper_level must be larger than lower_level");
+    check_levels(lower_level, upper_level);
+    pre_filled();
 
-    _filled = true;
     _lower_level = lower_level;
     _upper_level = upper_level;
-
-    _identify_holes = !(_fill_type == FillType::ChunkCombinedCode ||
-                        _fill_type == FillType::ChunkCombinedOffset);
-    _output_chunked = !(_fill_type == FillType::OuterCode || _fill_type == FillType::OuterOffset);
-    _direct_points = _output_chunked;
-    _direct_line_offsets = (_fill_type == FillType::ChunkCombinedOffset||
-                            _fill_type == FillType::ChunkCombinedOffsetOffset);
-    _direct_outer_offsets = (_fill_type == FillType::ChunkCombinedCodeOffset ||
-                             _fill_type == FillType::ChunkCombinedOffsetOffset);
-    _outer_offsets_into_points = (_fill_type == FillType::ChunkCombinedCodeOffset);
-    _nan_separated = false;
-    _return_list_count = (_fill_type == FillType::ChunkCombinedCodeOffset ||
-                          _fill_type == FillType::ChunkCombinedOffsetOffset) ? 3 : 2;
-
     return march_wrapper();
 }
 
@@ -1878,12 +1863,6 @@ void BaseContourGenerator<Derived>::interp(
     *points++ = get_point_y(point0)*frac + y1*(1.0 - frac);
 }
 
-template <typename Derived>
-bool BaseContourGenerator<Derived>::is_filled() const
-{
-    return _filled;
-}
-
 template <typename Derived>
 bool BaseContourGenerator<Derived>::is_point_in_chunk(index_t point, const ChunkLocal& local) const
 {
@@ -1942,22 +1921,9 @@ void BaseContourGenerator<Derived>::line(const Location& start_location, ChunkLo
 template <typename Derived>
 py::sequence BaseContourGenerator<Derived>::lines(double level)
 {
-    _filled = false;
-    _lower_level = _upper_level = level;
-
-    _identify_holes = false;
-    _output_chunked = !(_line_type == LineType::Separate || _line_type == LineType::SeparateCode);
-    _direct_points = _output_chunked;
-    _direct_line_offsets = (_line_type == LineType::ChunkCombinedOffset);
-    _direct_outer_offsets = false;
-    _outer_offsets_into_points = false;
-    _return_list_count = (_line_type == LineType::Separate ||
-                          _line_type == LineType::ChunkCombinedNan) ? 1 : 2;
-    _nan_separated = (_line_type == LineType::ChunkCombinedNan);
-
-    if (_nan_separated)
-        Util::ensure_nan_loaded();
+    pre_lines();
 
+    _lower_level = _upper_level = level;
     return march_wrapper();
 }
 
@@ -2357,6 +2323,82 @@ void BaseContourGenerator<Derived>::move_to_next_boundary_edge(
     }
 }
 
+template <typename Derived>
+py::list BaseContourGenerator<Derived>::multi_filled(const LevelArray levels)
+{
+    check_levels(levels, true);
+    pre_filled();
+
+    auto levels_proxy = levels.unchecked<1>();
+    auto n = levels_proxy.size();
+
+    py::list ret(n-1);
+    _lower_level = levels_proxy[0];
+    for (decltype(n) i = 0; i < n-1; i++) {
+        _upper_level = levels_proxy[i+1];
+        ret[i] = march_wrapper();
+        _lower_level = _upper_level;
+    }
+
+    return ret;
+}
+
+template <typename Derived>
+py::list BaseContourGenerator<Derived>::multi_lines(const LevelArray levels)
+{
+    check_levels(levels, false);
+    pre_lines();
+
+    auto levels_proxy = levels.unchecked<1>();
+    auto n = levels_proxy.size();
+
+    py::list ret(n);
+    for (decltype(n) i = 0; i < n; i++) {
+        _lower_level = _upper_level = levels_proxy[i];
+        ret[i] = march_wrapper();
+    }
+
+    return ret;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::pre_filled()
+{
+    _filled = true;
+
+    _identify_holes = !(_fill_type == FillType::ChunkCombinedCode ||
+                        _fill_type == FillType::ChunkCombinedOffset);
+    _output_chunked = !(_fill_type == FillType::OuterCode || _fill_type == FillType::OuterOffset);
+    _direct_points = _output_chunked;
+    _direct_line_offsets = (_fill_type == FillType::ChunkCombinedOffset||
+                            _fill_type == FillType::ChunkCombinedOffsetOffset);
+    _direct_outer_offsets = (_fill_type == FillType::ChunkCombinedCodeOffset ||
+                             _fill_type == FillType::ChunkCombinedOffsetOffset);
+    _outer_offsets_into_points = (_fill_type == FillType::ChunkCombinedCodeOffset);
+    _nan_separated = false;
+    _return_list_count = (_fill_type == FillType::ChunkCombinedCodeOffset ||
+                          _fill_type == FillType::ChunkCombinedOffsetOffset) ? 3 : 2;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::pre_lines()
+{
+    _filled = false;
+
+    _identify_holes = false;
+    _output_chunked = !(_line_type == LineType::Separate || _line_type == LineType::SeparateCode);
+    _direct_points = _output_chunked;
+    _direct_line_offsets = (_line_type == LineType::ChunkCombinedOffset);
+    _direct_outer_offsets = false;
+    _outer_offsets_into_points = false;
+    _return_list_count = (_line_type == LineType::Separate ||
+                          _line_type == LineType::ChunkCombinedNan) ? 1 : 2;
+    _nan_separated = (_line_type == LineType::ChunkCombinedNan);
+
+    if (_nan_separated)
+        Util::ensure_nan_loaded();
+}
+
 template <typename Derived>
 void BaseContourGenerator<Derived>::set_look_flags(index_t hole_start_quad)
 {
diff --git a/contrib/python/contourpy/src/common.h b/contrib/python/contourpy/src/common.h
index cc4bc851a0..68c522d1b6 100644
--- a/contrib/python/contourpy/src/common.h
+++ b/contrib/python/contourpy/src/common.h
@@ -21,6 +21,7 @@ typedef uint32_t offset_t;
 // Input numpy array classes.
 typedef py::array_t<double, py::array::c_style | py::array::forcecast> CoordinateArray;
 typedef py::array_t<bool,   py::array::c_style | py::array::forcecast> MaskArray;
+typedef py::array_t<double> LevelArray;  // Doesn't have to be contiguous.
 
 // Output numpy array classes.
 typedef py::array_t<double>   PointArray;
diff --git a/contrib/python/contourpy/src/contour_generator.cpp b/contrib/python/contourpy/src/contour_generator.cpp
new file mode 100644
index 0000000000..5ede6fa46e
--- /dev/null
+++ b/contrib/python/contourpy/src/contour_generator.cpp
@@ -0,0 +1,79 @@
+#include "contour_generator.h"
+#include "util.h"
+
+namespace contourpy {
+
+void ContourGenerator::check_levels(const LevelArray& levels, bool filled) const
+{
+    if (levels.ndim() != 1) {
+        throw std::domain_error(
+            "Levels array must be 1D not " + std::to_string(levels.ndim()) + "D");
+    }
+
+    if (filled) {
+        auto n = levels.size();
+        if (n < 2) {
+            throw std::invalid_argument(
+                "Levels array must have at least 2 elements, not " + std::to_string(n));
+        }
+
+        auto levels_proxy = levels.unchecked<1>();
+
+        // Check levels are not NaN.
+        for (decltype(n) i = 0; i < n; i++) {
+            if (Util::is_nan(levels_proxy[i]))
+                throw std::invalid_argument("Levels must not contain any NaN");
+        }
+
+        // Check levels are increasing.
+        auto lower_level = levels_proxy[0];
+        for (decltype(n) i = 0; i < n-1; i++) {
+            auto upper_level = levels_proxy[i+1];
+            if (lower_level >= upper_level)
+                throw std::invalid_argument("Levels must be increasing");
+            lower_level = upper_level;
+        }
+    }
+}
+
+void ContourGenerator::check_levels(double lower_level, double upper_level) const
+{
+    if (Util::is_nan(lower_level) || Util::is_nan(upper_level))
+        throw std::invalid_argument("lower_level and upper_level cannot be NaN");
+    if (lower_level >= upper_level)
+        throw std::invalid_argument("upper_level must be larger than lower_level");
+}
+
+py::list ContourGenerator::multi_filled(const LevelArray levels)
+{
+    check_levels(levels, true);
+
+    auto levels_proxy = levels.unchecked<1>();
+    auto n = levels_proxy.size();
+
+    py::list ret(n-1);
+    auto lower_level = levels_proxy[0];
+    for (decltype(n) i = 0; i < n-1; i++) {
+        auto upper_level = levels_proxy[i+1];
+        ret[i] = filled(lower_level, upper_level);
+        lower_level = upper_level;
+    }
+
+    return ret;
+}
+
+py::list ContourGenerator::multi_lines(const LevelArray levels)
+{
+    check_levels(levels, false);
+
+    auto levels_proxy = levels.unchecked<1>();
+    auto n = levels_proxy.size();
+
+    py::list ret(n);
+    for (decltype(n) i = 0; i < n; i++)
+        ret[i] = lines(levels_proxy[i]);
+
+    return ret;
+}
+
+} // namespace contourpy
diff --git a/contrib/python/contourpy/src/contour_generator.h b/contrib/python/contourpy/src/contour_generator.h
index ce527b25f8..c437580023 100644
--- a/contrib/python/contourpy/src/contour_generator.h
+++ b/contrib/python/contourpy/src/contour_generator.h
@@ -14,8 +14,21 @@ public:
     ContourGenerator& operator=(const ContourGenerator& other) = delete;
     ContourGenerator& operator=(const ContourGenerator&& other) = delete;
 
+    virtual ~ContourGenerator() = default;
+
+    virtual py::tuple filled(double lower_level, double upper_level) = 0;
+
+    virtual py::sequence lines(double level) = 0;
+
+    virtual py::list multi_filled(const LevelArray levels);
+    virtual py::list multi_lines(const LevelArray levels);
+
 protected:
     ContourGenerator() = default;
+
+    // Check levels are acceptable, throw exception if not.
+    void check_levels(const LevelArray& levels, bool filled) const;
+    void check_levels(double lower_level, double upper_level) const;
 };
 
 } // namespace contourpy
diff --git a/contrib/python/contourpy/src/converter.cpp b/contrib/python/contourpy/src/converter.cpp
index 1f693ceb1a..6a800e2ec8 100644
--- a/contrib/python/contourpy/src/converter.cpp
+++ b/contrib/python/contourpy/src/converter.cpp
@@ -13,7 +13,7 @@ void Converter::check_max_offset(count_t max_offset)
 CodeArray Converter::convert_codes(
     count_t point_count, count_t cut_count, const offset_t* cut_start, offset_t subtract)
 {
-    assert(point_count > 0 && cut_count > 0 && subtract >= 0);
+    assert(point_count > 0 && cut_count > 0);
     assert(cut_start != nullptr);
 
     index_t codes_shape = static_cast<index_t>(point_count);
@@ -26,7 +26,7 @@ void Converter::convert_codes(
     count_t point_count, count_t cut_count, const offset_t* cut_start, offset_t subtract,
     CodeArray::value_type* codes)
 {
-    assert(point_count > 0 && cut_count > 0 && subtract >= 0);
+    assert(point_count > 0 && cut_count > 0);
     assert(cut_start != nullptr);
     assert(codes != nullptr);
 
@@ -105,7 +105,7 @@ void Converter::convert_codes_check_closed_single(
 OffsetArray Converter::convert_offsets(
     count_t offset_count, const offset_t* start, offset_t subtract)
 {
-    assert(offset_count > 0 && subtract >= 0);
+    assert(offset_count > 0);
     assert(start != nullptr);
 
     index_t offsets_shape = static_cast<index_t>(offset_count);
@@ -118,7 +118,7 @@ void Converter::convert_offsets(
     count_t offset_count, const offset_t* start, offset_t subtract,
     OffsetArray::value_type* offsets)
 {
-    assert(offset_count > 0 && subtract >= 0);
+    assert(offset_count > 0);
     assert(start != nullptr);
     assert(offsets != nullptr);
 
diff --git a/contrib/python/contourpy/src/mpl2005.cpp b/contrib/python/contourpy/src/mpl2005.cpp
index a62d6e86ce..d94432a44f 100644
--- a/contrib/python/contourpy/src/mpl2005.cpp
+++ b/contrib/python/contourpy/src/mpl2005.cpp
@@ -46,11 +46,9 @@ Mpl2005ContourGenerator::~Mpl2005ContourGenerator()
     cntr_del(_site);
 }
 
-py::tuple Mpl2005ContourGenerator::filled(const double& lower_level, const double& upper_level)
+py::tuple Mpl2005ContourGenerator::filled(double lower_level, double upper_level)
 {
-    if (lower_level >= upper_level)
-        throw std::invalid_argument("upper_level must be larger than lower_level");
-
+    check_levels(lower_level, upper_level);
     double levels[2] = {lower_level, upper_level};
     return cntr_trace(_site, levels, 2);
 }
@@ -67,7 +65,7 @@ py::tuple Mpl2005ContourGenerator::get_chunk_size() const
     return py::make_tuple(_site->j_chunk_size, _site->i_chunk_size);
 }
 
-py::tuple Mpl2005ContourGenerator::lines(const double& level)
+py::sequence Mpl2005ContourGenerator::lines(double level)
 {
     double levels[2] = {level, 0.0};
     return cntr_trace(_site, levels, 1);
diff --git a/contrib/python/contourpy/src/mpl2005.h b/contrib/python/contourpy/src/mpl2005.h
index e112880765..5801f5d17b 100644
--- a/contrib/python/contourpy/src/mpl2005.h
+++ b/contrib/python/contourpy/src/mpl2005.h
@@ -13,14 +13,14 @@ public:
         const CoordinateArray& x, const CoordinateArray& y, const CoordinateArray& z,
         const MaskArray& mask, index_t x_chunk_size, index_t y_chunk_size);
 
-    ~Mpl2005ContourGenerator();
+    virtual ~Mpl2005ContourGenerator();
 
-    py::tuple filled(const double& lower_level, const double& upper_level);
+    py::tuple filled(double lower_level, double upper_level) override;
 
     py::tuple get_chunk_count() const;  // Return (y_chunk_count, x_chunk_count)
     py::tuple get_chunk_size() const;   // Return (y_chunk_size, x_chunk_size)
 
-    py::tuple lines(const double& level);
+    py::sequence lines(double level) override;
 
 private:
     CoordinateArray _x, _y, _z;
diff --git a/contrib/python/contourpy/src/mpl2014.cpp b/contrib/python/contourpy/src/mpl2014.cpp
index a363fa8768..e1021572a7 100644
--- a/contrib/python/contourpy/src/mpl2014.cpp
+++ b/contrib/python/contourpy/src/mpl2014.cpp
@@ -483,16 +483,13 @@ void Mpl2014ContourGenerator::edge_interp(
            level, contour_line);
 }
 
-py::tuple Mpl2014ContourGenerator::filled(
-    const double& lower_level, const double& upper_level)
+py::tuple Mpl2014ContourGenerator::filled(double lower_level, double upper_level)
 {
-    if (lower_level >= upper_level)
-        throw std::invalid_argument("upper_level must be larger than lower_level");
+    check_levels(lower_level, upper_level);
 
     init_cache_levels(lower_level, upper_level);
 
     Contour contour;
-
     py::list vertices, codes;
 
     index_t ichunk, jchunk, istart, iend, jstart, jend;
@@ -1218,7 +1215,7 @@ bool Mpl2014ContourGenerator::is_edge_a_boundary(
     }
 }
 
-py::tuple Mpl2014ContourGenerator::lines(const double& level)
+py::sequence Mpl2014ContourGenerator::lines(double level)
 {
     init_cache_levels(level, level);
 
diff --git a/contrib/python/contourpy/src/mpl2014.h b/contrib/python/contourpy/src/mpl2014.h
index 105ce23c1f..5872e8ddcf 100644
--- a/contrib/python/contourpy/src/mpl2014.h
+++ b/contrib/python/contourpy/src/mpl2014.h
@@ -270,11 +270,11 @@ public:
         const MaskArray& mask, bool corner_mask, index_t x_chunk_size, index_t y_chunk_size);
 
     // Destructor.
-    ~Mpl2014ContourGenerator();
+    virtual ~Mpl2014ContourGenerator();
 
     // Create and return polygons for a filled contour between the two
     // specified levels.
-    py::tuple filled(const double& lower_level, const double& upper_level);
+    py::tuple filled(double lower_level, double upper_level) override;
 
     py::tuple get_chunk_count() const;  // Return (y_chunk_count, x_chunk_count)
     py::tuple get_chunk_size() const;   // Return (y_chunk_size, x_chunk_size)
@@ -283,7 +283,7 @@ public:
 
     // Create and return polygons for a line (i.e. non-filled) contour at the
     // specified level.
-    py::tuple lines(const double& level);
+    py::sequence lines(double level) override;
 
 private:
     // Typedef for following either a boundary of the domain or the interior;
diff --git a/contrib/python/contourpy/src/util.cpp b/contrib/python/contourpy/src/util.cpp
index 59b6e4da55..82c1cc21d9 100644
--- a/contrib/python/contourpy/src/util.cpp
+++ b/contrib/python/contourpy/src/util.cpp
@@ -1,4 +1,5 @@
 #include "util.h"
+#include <cmath>
 #include <thread>
 
 namespace contourpy {
@@ -21,4 +22,9 @@ index_t Util::get_max_threads()
     return static_cast<index_t>(std::thread::hardware_concurrency());
 }
 
+bool Util::is_nan(double value)
+{
+    return std::isnan(value);
+}
+
 } // namespace contourpy
diff --git a/contrib/python/contourpy/src/util.h b/contrib/python/contourpy/src/util.h
index f4a1abda79..dcafd131c1 100644
--- a/contrib/python/contourpy/src/util.h
+++ b/contrib/python/contourpy/src/util.h
@@ -12,6 +12,8 @@ public:
 
     static index_t get_max_threads();
 
+    static bool is_nan(double value);
+
     // This is the NaN used internally and returned to calling functions. The value is taken from
     // numpy rather than the standard C++ approach so that it is guaranteed to work with
     // numpy.isnan(). The value is actually the same for many platforms, but this approach
diff --git a/contrib/python/contourpy/src/wrap.cpp b/contrib/python/contourpy/src/wrap.cpp
index b784f814a5..fede228095 100644
--- a/contrib/python/contourpy/src/wrap.cpp
+++ b/contrib/python/contourpy/src/wrap.cpp
@@ -13,18 +13,19 @@
 #define MACRO_STRINGIFY(x) STRINGIFY(x)
 
 namespace py = pybind11;
+using namespace pybind11::literals;
 
 static contourpy::LineType mpl20xx_line_type = contourpy::LineType::SeparateCode;
 static contourpy::FillType mpl20xx_fill_type = contourpy::FillType::OuterCode;
 
-PYBIND11_MODULE(_contourpy, m) {
+PYBIND11_MODULE(_contourpy, m, py::mod_gil_not_used()) {
     m.doc() =
         "C++11 extension module wrapped using `pybind11`_.\n\n"
         "It should not be necessary to access classes and functions in this extension module "
-        "directly. Instead, :func:`contourpy.contour_generator` should be used to create "
-        ":class:`~contourpy.ContourGenerator` objects, and the enums "
-        "(:class:`~contourpy.FillType`, :class:`~contourpy.LineType` and "
-        ":class:`~contourpy.ZInterp`) and :func:`contourpy.max_threads` function are all available "
+        "directly. Instead, :func:`~contourpy.contour_generator` should be used to create "
+        ":class:`~.ContourGenerator` objects, and the enums "
+        "(:class:`~.FillType`, :class:`~.LineType` and "
+        ":class:`~.ZInterp`) and :func:`.max_threads` function are all available "
         "in the :mod:`contourpy` module.";
 
     m.attr("__version__") = MACRO_STRINGIFY(CONTOURPY_VERSION);
@@ -38,9 +39,9 @@ PYBIND11_MODULE(_contourpy, m) {
 #endif
 
     py::enum_<contourpy::FillType>(m, "FillType",
-        "Enum used for ``fill_type`` keyword argument in :func:`~contourpy.contour_generator`.\n\n"
+        "Enum used for ``fill_type`` keyword argument in :func:`~.contour_generator`.\n\n"
         "This controls the format of filled contour data returned from "
-        ":meth:`~contourpy.ContourGenerator.filled`.")
+        ":meth:`~.ContourGenerator.filled`.")
         .value("OuterCode", contourpy::FillType::OuterCode)
         .value("OuterOffset", contourpy::FillType::OuterOffset)
         .value("ChunkCombinedCode", contourpy::FillType::ChunkCombinedCode)
@@ -50,9 +51,9 @@ PYBIND11_MODULE(_contourpy, m) {
         .export_values();
 
     py::enum_<contourpy::LineType>(m, "LineType",
-        "Enum used for ``line_type`` keyword argument in :func:`~contourpy.contour_generator`.\n\n"
+        "Enum used for ``line_type`` keyword argument in :func:`~.contour_generator`.\n\n"
         "This controls the format of contour line data returned from "
-        ":meth:`~contourpy.ContourGenerator.lines`.\n\n"
+        ":meth:`~.ContourGenerator.lines`.\n\n"
         "``LineType.ChunkCombinedNan`` added in version 1.2.0")
         .value("Separate", contourpy::LineType::Separate)
         .value("SeparateCode", contourpy::LineType::SeparateCode)
@@ -62,7 +63,7 @@ PYBIND11_MODULE(_contourpy, m) {
         .export_values();
 
     py::enum_<contourpy::ZInterp>(m, "ZInterp",
-        "Enum used for ``z_interp`` keyword argument in :func:`~contourpy.contour_generator`\n\n"
+        "Enum used for ``z_interp`` keyword argument in :func:`~.contour_generator`\n\n"
         "This controls the interpolation used on ``z`` values to determine where contour lines "
         "intersect the edges of grid quads, and ``z`` values at quad centres.")
         .value("Linear", contourpy::ZInterp::Linear)
@@ -73,47 +74,88 @@ PYBIND11_MODULE(_contourpy, m) {
         "Return the maximum number of threads, obtained from "
         "``std::thread::hardware_concurrency()``.\n\n"
         "This is the number of threads used by a multithreaded ContourGenerator if the kwarg "
-        "``threads=0`` is passed to :func:`~contourpy.contour_generator`.");
+        "``threads=0`` is passed to :func:`~.contour_generator`.");
 
     const char* chunk_count_doc = "Return tuple of (y, x) chunk counts.";
     const char* chunk_size_doc = "Return tuple of (y, x) chunk sizes.";
     const char* corner_mask_doc = "Return whether ``corner_mask`` is set or not.";
     const char* create_contour_doc =
-        "Synonym for :func:`~contourpy.ContourGenerator.lines` to provide backward compatibility "
+        "Synonym for :meth:`~.ContourGenerator.lines` to provide backward compatibility "
         "with Matplotlib.";
     const char* create_filled_contour_doc =
-        "Synonym for :func:`~contourpy.ContourGenerator.filled` to provide backward compatibility "
+        "Synonym for :meth:`~.ContourGenerator.filled` to provide backward compatibility "
         "with Matplotlib.";
-    const char* default_fill_type_doc = "Return the default ``FillType`` used by this algorithm.";
-    const char* default_line_type_doc = "Return the default ``LineType`` used by this algorithm.";
-    const char* fill_type_doc = "Return the ``FillType``.";
+    const char* default_fill_type_doc =
+        "Return the default :class:`~.FillType` used by this algorithm.";
+    const char* default_line_type_doc =
+        "Return the default :class:`~.LineType` used by this algorithm.";
+    const char* fill_type_doc = "Return the :class:`~.FillType`.";
     const char* filled_doc =
         "Calculate and return filled contours between two levels.\n\n"
         "Args:\n"
-        "    lower_level (float): Lower z-level of the filled contours.\n"
-        "    upper_level (float): Upper z-level of the filled contours.\n"
+        "    lower_level (float): Lower z-level of the filled contours, cannot be ``np.nan``.\n"
+        "    upper_level (float): Upper z-level of the filled contours, cannot be ``np.nan``.\n"
         "Return:\n"
-        "    Filled contour polygons as one or more sequences of numpy arrays. The exact format is "
-        "determined by the ``fill_type`` used by the ``ContourGenerator``.\n\n"
-        "Raises a ``ValueError`` if ``lower_level >= upper_level``.\n\n"
+        "    Filled contour polygons as nested sequences of numpy arrays. The exact format is "
+        "determined by the :attr:`~.ContourGenerator.fill_type` used by the ``ContourGenerator`` "
+        "and the options are explained at :ref:`fill_type`.\n\n"
+        "Raises a ``ValueError`` if ``lower_level >= upper_level`` or if\n"
+        "``lower_level`` or ``upper_level`` are ``np.nan``.\n\n"
         "To return filled contours below a ``level`` use ``filled(-np.inf, level)``.\n"
         "To return filled contours above a ``level`` use ``filled(level, np.inf)``";
-    const char* line_type_doc = "Return the ``LineType``.";
+    const char* line_type_doc = "Return the :class:`~.LineType`.";
     const char* lines_doc =
         "Calculate and return contour lines at a particular level.\n\n"
         "Args:\n"
         "    level (float): z-level to calculate contours at.\n\n"
         "Return:\n"
-        "    Contour lines (open line strips and closed line loops) as one or more sequences of "
-        "numpy arrays. The exact format is determined by the ``line_type`` used by the "
-        "``ContourGenerator``.";
+        "    Contour lines (open line strips and closed line loops) as nested sequences of "
+        "numpy arrays. The exact format is determined by the :attr:`~.ContourGenerator.line_type` "
+        "used by the ``ContourGenerator`` and the options are explained at :ref:`line_type`.\n\n"
+        "``level`` may be ``np.nan``, ``np.inf`` or ``-np.inf``; they all return the same result "
+        "which is an empty line set.";
+    const char* multi_filled_doc =
+        "Calculate and return filled contours between multiple pairs of adjacent levels.\n\n"
+        "Args:\n"
+        "    levels (array-like of floats): z-levels to calculate filled contours between. "
+        "There must be at least 2 levels, they cannot be NaN, and each level must be larger than "
+        "the previous level.\n\n"
+        "Return:\n"
+        "    List of filled contours, one per pair of levels. The length of the returned list is "
+        "one less than ``len(levels)``. The format of returned contours is determined by the "
+        "``fill_type`` used by the ``ContourGenerator`` and the options are explained at "
+        ":ref:`fill_type`.\n\n"
+        "Raises a ``ValueError`` if ``levels`` are not increasing, or contain a NaN, or there are "
+        "fewer than 2 ``levels``.\n\n"
+        "Calling:\n\n"
+        ".. code-block:: python\n\n"
+        "    ret = cont_gen.multi_filled(levels)\n\n"
+        "is equivalent to:\n\n"
+        ".. code-block:: python\n\n"
+        "    ret = [cont_gen.filled(lower, upper) for lower, upper in zip(levels[:-1], levels[1:])]\n\n"
+        ".. versionadded:: 1.3.0";
+    const char* multi_lines_doc =
+        "Calculate and return contour lines at multiple levels.\n\n"
+        "Args:\n"
+        "    levels (array-like of floats): z-levels to calculate contours at.\n\n"
+        "Return:\n"
+        "    List of contour lines, one per level. The format of returned lines is determined by "
+        "the ``line_type`` used by the ``ContourGenerator`` and the options are explained at "
+        ":ref:`line_type`.\n\n"
+        "Calling:\n\n"
+        ".. code-block:: python\n\n"
+        "    ret = cont_gen.multi_lines(levels)\n\n"
+        "is equivalent to:\n\n"
+        ".. code-block:: python\n\n"
+        "    ret = [cont_gen.lines(level) for level in levels]\n\n"
+        ".. versionadded:: 1.3.0";
     const char* quad_as_tri_doc = "Return whether ``quad_as_tri`` is set or not.";
     const char* supports_corner_mask_doc =
         "Return whether this algorithm supports ``corner_mask``.";
     const char* supports_fill_type_doc =
-        "Return whether this algorithm supports a particular ``FillType``.";
+        "Return whether this algorithm supports a particular :class:`~.FillType`.";
     const char* supports_line_type_doc =
-        "Return whether this algorithm supports a particular ``LineType``.";
+        "Return whether this algorithm supports a particular :class:`~.LineType`.";
     const char* supports_quad_as_tri_doc =
         "Return whether this algorithm supports ``quad_as_tri``.";
     const char* supports_threads_doc =
@@ -127,18 +169,15 @@ PYBIND11_MODULE(_contourpy, m) {
     py::class_<contourpy::ContourGenerator>(m, "ContourGenerator",
         "Abstract base class for contour generator classes, defining the interface that they all "
         "implement.")
-        .def("create_contour",
-            [](py::object /* self */, double level) {return py::make_tuple();},
-            py::arg("level"), create_contour_doc)
-        .def("create_filled_contour",
-            [](py::object /* self */, double lower_level, double upper_level) {return py::make_tuple();},
-            py::arg("lower_level"), py::arg("upper_level"), create_filled_contour_doc)
-        .def("filled",
-            [](py::object /* self */, double lower_level, double upper_level) {return py::make_tuple();},
-            py::arg("lower_level"), py::arg("upper_level"), filled_doc)
-        .def("lines",
-            [](py::object /* self */, double level) {return py::make_tuple();},
-            py::arg("level"), lines_doc)
+        .def("create_contour", &contourpy::ContourGenerator::lines, create_contour_doc, "level"_a)
+        .def("create_filled_contour", &contourpy::ContourGenerator::filled,
+             create_filled_contour_doc, "lower_level"_a, "upper_level"_a)
+        .def("filled", &contourpy::ContourGenerator::filled, filled_doc,
+             "lower_level"_a, "upper_level"_a)
+        .def("lines", &contourpy::ContourGenerator::lines, lines_doc, "level"_a)
+        .def("multi_filled", &contourpy::ContourGenerator::multi_filled, multi_filled_doc,
+             "levels"_a)
+        .def("multi_lines", &contourpy::ContourGenerator::multi_lines, multi_lines_doc, "levels"_a)
         .def_property_readonly(
             "chunk_count", [](py::object /* self */) {return py::make_tuple(1, 1);},
             chunk_count_doc)
@@ -169,10 +208,10 @@ PYBIND11_MODULE(_contourpy, m) {
         .def_static("supports_corner_mask", []() {return false;}, supports_corner_mask_doc)
         .def_static(
             "supports_fill_type", [](contourpy::FillType /* fill_type */) {return false;},
-            py::arg("fill_type"), supports_fill_type_doc)
+            "fill_type"_a, supports_fill_type_doc)
         .def_static(
             "supports_line_type", [](contourpy::LineType /* line_type */) {return false;},
-            py::arg("line_type"), supports_line_type_doc)
+            "line_type"_a, supports_line_type_doc)
         .def_static("supports_quad_as_tri", []() {return false;}, supports_quad_as_tri_doc)
         .def_static("supports_threads", []() {return false;}, supports_threads_doc)
         .def_static("supports_z_interp", []() {return false;}, supports_z_interp_doc);
@@ -193,18 +232,8 @@ PYBIND11_MODULE(_contourpy, m) {
                       const contourpy::MaskArray&,
                       contourpy::index_t,
                       contourpy::index_t>(),
-             py::arg("x"),
-             py::arg("y"),
-             py::arg("z"),
-             py::arg("mask"),
-             py::kw_only(),
-             py::arg("x_chunk_size") = 0,
-             py::arg("y_chunk_size") = 0)
-        .def("create_contour", &contourpy::Mpl2005ContourGenerator::lines, create_contour_doc)
-        .def("create_filled_contour", &contourpy::Mpl2005ContourGenerator::filled,
-            create_filled_contour_doc)
-        .def("filled", &contourpy::Mpl2005ContourGenerator::filled, filled_doc)
-        .def("lines", &contourpy::Mpl2005ContourGenerator::lines, lines_doc)
+             "x"_a, "y"_a, "z"_a, "mask"_a, py::kw_only(), "x_chunk_size"_a = 0,
+             "y_chunk_size"_a = 0)
         .def_property_readonly(
             "chunk_count", &contourpy::Mpl2005ContourGenerator::get_chunk_count, chunk_count_doc)
         .def_property_readonly(
@@ -247,20 +276,8 @@ PYBIND11_MODULE(_contourpy, m) {
                       bool,
                       contourpy::index_t,
                       contourpy::index_t>(),
-             py::arg("x"),
-             py::arg("y"),
-             py::arg("z"),
-             py::arg("mask"),
-             py::kw_only(),
-             py::arg("corner_mask"),
-             py::arg("x_chunk_size") = 0,
-             py::arg("y_chunk_size") = 0)
-        .def("create_contour", &contourpy::mpl2014::Mpl2014ContourGenerator::lines,
-            create_contour_doc)
-        .def("create_filled_contour", &contourpy::mpl2014::Mpl2014ContourGenerator::filled,
-            create_filled_contour_doc)
-        .def("filled", &contourpy::mpl2014::Mpl2014ContourGenerator::filled, filled_doc)
-        .def("lines", &contourpy::mpl2014::Mpl2014ContourGenerator::lines, lines_doc)
+             "x"_a, "y"_a, "z"_a, "mask"_a, py::kw_only(), "corner_mask"_a, "x_chunk_size"_a = 0,
+             "y_chunk_size"_a = 0)
         .def_property_readonly(
             "chunk_count", &contourpy::mpl2014::Mpl2014ContourGenerator::get_chunk_count,
             chunk_count_doc)
@@ -307,24 +324,10 @@ PYBIND11_MODULE(_contourpy, m) {
                       contourpy::ZInterp,
                       contourpy::index_t,
                       contourpy::index_t>(),
-             py::arg("x"),
-             py::arg("y"),
-             py::arg("z"),
-             py::arg("mask"),
-             py::kw_only(),
-             py::arg("corner_mask"),
-             py::arg("line_type"),
-             py::arg("fill_type"),
-             py::arg("quad_as_tri"),
-             py::arg("z_interp"),
-             py::arg("x_chunk_size") = 0,
-             py::arg("y_chunk_size") = 0)
+             "x"_a, "y"_a, "z"_a, "mask"_a, py::kw_only(), "corner_mask"_a, "line_type"_a,
+             "fill_type"_a, "quad_as_tri"_a, "z_interp"_a, "x_chunk_size"_a = 0,
+             "y_chunk_size"_a = 0)
         .def("_write_cache", &contourpy::SerialContourGenerator::write_cache)
-        .def("create_contour", &contourpy::SerialContourGenerator::lines, create_contour_doc)
-        .def("create_filled_contour", &contourpy::SerialContourGenerator::filled,
-            create_filled_contour_doc)
-        .def("filled", &contourpy::SerialContourGenerator::filled, filled_doc)
-        .def("lines", &contourpy::SerialContourGenerator::lines, lines_doc)
         .def_property_readonly(
             "chunk_count", &contourpy::SerialContourGenerator::get_chunk_count, chunk_count_doc)
         .def_property_readonly(
@@ -359,7 +362,7 @@ PYBIND11_MODULE(_contourpy, m) {
     py::class_<contourpy::ThreadedContourGenerator, contourpy::ContourGenerator>(
         m, "ThreadedContourGenerator",
         "ContourGenerator corresponding to ``name=\"threaded\"``, the multithreaded version of "
-        ":class:`~contourpy._contourpy.SerialContourGenerator`.\n\n"
+        ":class:`~.SerialContourGenerator`.\n\n"
         "Supports ``corner_mask``, ``quad_as_tri`` and ``z_interp`` and ``threads``. "
         "Supports all options for ``line_type`` and ``fill_type``.")
         .def(py::init<const contourpy::CoordinateArray&,
@@ -374,25 +377,10 @@ PYBIND11_MODULE(_contourpy, m) {
                       contourpy::index_t,
                       contourpy::index_t,
                       contourpy::index_t>(),
-             py::arg("x"),
-             py::arg("y"),
-             py::arg("z"),
-             py::arg("mask"),
-             py::kw_only(),
-             py::arg("corner_mask"),
-             py::arg("line_type"),
-             py::arg("fill_type"),
-             py::arg("quad_as_tri"),
-             py::arg("z_interp"),
-             py::arg("x_chunk_size") = 0,
-             py::arg("y_chunk_size") = 0,
-             py::arg("thread_count") = 0)
+             "x"_a, "y"_a, "z"_a, "mask"_a, py::kw_only(), "corner_mask"_a, "line_type"_a,
+             "fill_type"_a, "quad_as_tri"_a, "z_interp"_a, "x_chunk_size"_a = 0,
+             "y_chunk_size"_a = 0, "thread_count"_a = 0)
         .def("_write_cache", &contourpy::ThreadedContourGenerator::write_cache)
-        .def("create_contour", &contourpy::ThreadedContourGenerator::lines, create_contour_doc)
-        .def("create_filled_contour", &contourpy::ThreadedContourGenerator::filled,
-            create_filled_contour_doc)
-        .def("filled", &contourpy::ThreadedContourGenerator::filled, filled_doc)
-        .def("lines", &contourpy::ThreadedContourGenerator::lines, lines_doc)
         .def_property_readonly(
             "chunk_count", &contourpy::ThreadedContourGenerator::get_chunk_count, chunk_count_doc)
         .def_property_readonly(
-- 
cgit v1.2.3