diff --git a/Makefile.dep b/Makefile.dep index d25bd670ba..1ea49eac45 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -35114,3 +35114,133 @@ test/flatzinc/on_restart_sol_set$(OBJSUFFIX) test/flatzinc/on_restart_sol_set$(S ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ ./gecode/support/timer.hpp ./test/flatzinc.hh ./test/test.hh \ ./test/test.hpp +gecode/int/min-distance$(OBJSUFFIX) gecode/int/min-distance$(SBJSUFFIX): \ + ./gecode/int.hh ./gecode/int/array-traits.hpp ./gecode/int/array.hpp \ + ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp ./gecode/int/branch/afc.hpp \ + ./gecode/int/branch/assign.hpp ./gecode/int/branch/chb.hpp ./gecode/int/branch/traits.hpp \ + ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp ./gecode/int/channel.hpp \ + ./gecode/int/div.hh ./gecode/int/div.hpp ./gecode/int/exception.hpp \ + ./gecode/int/extensional.hpp ./gecode/int/extensional/dfa.hpp ./gecode/int/extensional/tuple-set.hpp \ + ./gecode/int/int-set-1.hpp ./gecode/int/int-set-2.hpp ./gecode/int/ipl.hpp \ + ./gecode/int/irt.hpp ./gecode/int/limits.hpp ./gecode/int/min-distance.hh \ + ./gecode/int/min-distance.hpp ./gecode/int/min-distance/prop.hpp ./gecode/int/propagator.hpp \ + ./gecode/int/reify.hpp ./gecode/int/trace.hpp ./gecode/int/trace/bool-delta.hpp \ + ./gecode/int/trace/bool-trace-view.hpp ./gecode/int/trace/int-delta.hpp ./gecode/int/trace/int-trace-view.hpp \ + ./gecode/int/trace/traits.hpp ./gecode/int/var-imp.hpp ./gecode/int/var-imp/bool.hpp \ + ./gecode/int/var-imp/delta.hpp ./gecode/int/var-imp/int.hpp ./gecode/int/var/bool.hpp \ + ./gecode/int/var/int.hpp ./gecode/int/var/print.hpp ./gecode/int/view.hpp \ + ./gecode/int/view/bool-test.hpp ./gecode/int/view/bool.hpp ./gecode/int/view/cached.hpp \ + ./gecode/int/view/constint.hpp ./gecode/int/view/int.hpp ./gecode/int/view/iter.hpp \ + ./gecode/int/view/minus.hpp ./gecode/int/view/neg-bool.hpp ./gecode/int/view/offset.hpp \ + ./gecode/int/view/print.hpp ./gecode/int/view/rel-test.hpp ./gecode/int/view/scale.hpp \ + ./gecode/int/view/zero.hpp ./gecode/iter.hh ./gecode/iter/ranges-add.hpp \ + ./gecode/iter/ranges-append.hpp ./gecode/iter/ranges-array.hpp ./gecode/iter/ranges-cache.hpp \ + ./gecode/iter/ranges-compl.hpp ./gecode/iter/ranges-diff.hpp ./gecode/iter/ranges-empty.hpp \ + ./gecode/iter/ranges-inter.hpp ./gecode/iter/ranges-list.hpp ./gecode/iter/ranges-map.hpp \ + ./gecode/iter/ranges-minmax.hpp ./gecode/iter/ranges-minus.hpp ./gecode/iter/ranges-negative.hpp \ + ./gecode/iter/ranges-offset.hpp ./gecode/iter/ranges-operations.hpp ./gecode/iter/ranges-positive.hpp \ + ./gecode/iter/ranges-rangelist.hpp ./gecode/iter/ranges-scale.hpp ./gecode/iter/ranges-singleton-append.hpp \ + ./gecode/iter/ranges-singleton.hpp ./gecode/iter/ranges-size.hpp ./gecode/iter/ranges-union.hpp \ + ./gecode/iter/ranges-values.hpp ./gecode/iter/values-array.hpp ./gecode/iter/values-bitset.hpp \ + ./gecode/iter/values-bitsetoffset.hpp ./gecode/iter/values-inter.hpp ./gecode/iter/values-list.hpp \ + ./gecode/iter/values-map.hpp ./gecode/iter/values-minus.hpp ./gecode/iter/values-negative.hpp \ + ./gecode/iter/values-offset.hpp ./gecode/iter/values-positive.hpp ./gecode/iter/values-ranges.hpp \ + ./gecode/iter/values-singleton.hpp ./gecode/iter/values-union.hpp ./gecode/iter/values-unique.hpp \ + ./gecode/kernel.hh ./gecode/kernel/archive.hpp ./gecode/kernel/branch/action.hpp \ + ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp ./gecode/kernel/branch/filter.hpp \ + ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp ./gecode/kernel/branch/tiebreak.hpp \ + ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp ./gecode/kernel/branch/val-sel-commit.hpp \ + ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp ./gecode/kernel/branch/var.hpp \ + ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp ./gecode/kernel/branch/view.hpp \ + ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp ./gecode/kernel/data/rnd.hpp \ + ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp ./gecode/kernel/exception.hpp \ + ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp ./gecode/kernel/memory/allocators.hpp \ + ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp ./gecode/kernel/memory/region.hpp \ + ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp ./gecode/kernel/propagator/pattern.hpp \ + ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp ./gecode/kernel/range-list.hpp \ + ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp ./gecode/kernel/trace/filter.hpp \ + ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp ./gecode/kernel/trace/recorder.hpp \ + ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp ./gecode/kernel/var-imp.hpp \ + ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp ./gecode/kernel/view.hpp \ + ./gecode/search.hh ./gecode/search/bab.hpp ./gecode/search/base.hpp \ + ./gecode/search/build.hpp ./gecode/search/cutoff.hpp ./gecode/search/dfs.hpp \ + ./gecode/search/engine.hpp ./gecode/search/exception.hpp ./gecode/search/lds.hpp \ + ./gecode/search/options.hpp ./gecode/search/pbs.hpp ./gecode/search/rbs.hpp \ + ./gecode/search/sebs.hpp ./gecode/search/seq/dead.hh ./gecode/search/statistics.hpp \ + ./gecode/search/stop.hpp ./gecode/search/support.hh ./gecode/search/trace-recorder.hpp \ + ./gecode/search/tracer.hpp ./gecode/search/traits.hpp ./gecode/search/worker-control.hh \ + ./gecode/support.hh ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp \ + ./gecode/support/bitset-base.hpp ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp \ + ./gecode/support/block-allocator.hpp ./gecode/support/cast.hpp gecode/support/config.hpp \ + ./gecode/support/dynamic-array.hpp ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp \ + ./gecode/support/exception.hpp ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp \ + ./gecode/support/heap.hpp ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp \ + ./gecode/support/macros.hpp ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp \ + ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp \ + ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ + ./gecode/support/timer.hpp +test/int/min-distance$(OBJSUFFIX) test/int/min-distance$(SBJSUFFIX): \ + ./gecode/int.hh ./gecode/int/array-traits.hpp ./gecode/int/array.hpp \ + ./gecode/int/branch.hpp ./gecode/int/branch/action.hpp ./gecode/int/branch/afc.hpp \ + ./gecode/int/branch/assign.hpp ./gecode/int/branch/chb.hpp ./gecode/int/branch/traits.hpp \ + ./gecode/int/branch/val.hpp ./gecode/int/branch/var.hpp ./gecode/int/channel.hpp \ + ./gecode/int/div.hh ./gecode/int/div.hpp ./gecode/int/exception.hpp \ + ./gecode/int/extensional.hpp ./gecode/int/extensional/dfa.hpp ./gecode/int/extensional/tuple-set.hpp \ + ./gecode/int/int-set-1.hpp ./gecode/int/int-set-2.hpp ./gecode/int/ipl.hpp \ + ./gecode/int/irt.hpp ./gecode/int/limits.hpp ./gecode/int/min-distance.hpp \ + ./gecode/int/propagator.hpp ./gecode/int/reify.hpp ./gecode/int/trace.hpp \ + ./gecode/int/trace/bool-delta.hpp ./gecode/int/trace/bool-trace-view.hpp ./gecode/int/trace/int-delta.hpp \ + ./gecode/int/trace/int-trace-view.hpp ./gecode/int/trace/traits.hpp ./gecode/int/var-imp.hpp \ + ./gecode/int/var-imp/bool.hpp ./gecode/int/var-imp/delta.hpp ./gecode/int/var-imp/int.hpp \ + ./gecode/int/var/bool.hpp ./gecode/int/var/int.hpp ./gecode/int/var/print.hpp \ + ./gecode/int/view.hpp ./gecode/int/view/bool-test.hpp ./gecode/int/view/bool.hpp \ + ./gecode/int/view/cached.hpp ./gecode/int/view/constint.hpp ./gecode/int/view/int.hpp \ + ./gecode/int/view/iter.hpp ./gecode/int/view/minus.hpp ./gecode/int/view/neg-bool.hpp \ + ./gecode/int/view/offset.hpp ./gecode/int/view/print.hpp ./gecode/int/view/rel-test.hpp \ + ./gecode/int/view/scale.hpp ./gecode/int/view/zero.hpp ./gecode/iter.hh \ + ./gecode/iter/ranges-add.hpp ./gecode/iter/ranges-append.hpp ./gecode/iter/ranges-array.hpp \ + ./gecode/iter/ranges-cache.hpp ./gecode/iter/ranges-compl.hpp ./gecode/iter/ranges-diff.hpp \ + ./gecode/iter/ranges-empty.hpp ./gecode/iter/ranges-inter.hpp ./gecode/iter/ranges-list.hpp \ + ./gecode/iter/ranges-map.hpp ./gecode/iter/ranges-minmax.hpp ./gecode/iter/ranges-minus.hpp \ + ./gecode/iter/ranges-negative.hpp ./gecode/iter/ranges-offset.hpp ./gecode/iter/ranges-operations.hpp \ + ./gecode/iter/ranges-positive.hpp ./gecode/iter/ranges-rangelist.hpp ./gecode/iter/ranges-scale.hpp \ + ./gecode/iter/ranges-singleton-append.hpp ./gecode/iter/ranges-singleton.hpp ./gecode/iter/ranges-size.hpp \ + ./gecode/iter/ranges-union.hpp ./gecode/iter/ranges-values.hpp ./gecode/iter/values-array.hpp \ + ./gecode/iter/values-bitset.hpp ./gecode/iter/values-bitsetoffset.hpp ./gecode/iter/values-inter.hpp \ + ./gecode/iter/values-list.hpp ./gecode/iter/values-map.hpp ./gecode/iter/values-minus.hpp \ + ./gecode/iter/values-negative.hpp ./gecode/iter/values-offset.hpp ./gecode/iter/values-positive.hpp \ + ./gecode/iter/values-ranges.hpp ./gecode/iter/values-singleton.hpp ./gecode/iter/values-union.hpp \ + ./gecode/iter/values-unique.hpp ./gecode/kernel.hh ./gecode/kernel/archive.hpp \ + ./gecode/kernel/branch/action.hpp ./gecode/kernel/branch/afc.hpp ./gecode/kernel/branch/chb.hpp \ + ./gecode/kernel/branch/filter.hpp ./gecode/kernel/branch/merit.hpp ./gecode/kernel/branch/print.hpp \ + ./gecode/kernel/branch/tiebreak.hpp ./gecode/kernel/branch/traits.hpp ./gecode/kernel/branch/val-commit.hpp \ + ./gecode/kernel/branch/val-sel-commit.hpp ./gecode/kernel/branch/val-sel.hpp ./gecode/kernel/branch/val.hpp \ + ./gecode/kernel/branch/var.hpp ./gecode/kernel/branch/view-sel.hpp ./gecode/kernel/branch/view-val.hpp \ + ./gecode/kernel/branch/view.hpp ./gecode/kernel/core.hpp ./gecode/kernel/data/array.hpp \ + ./gecode/kernel/data/rnd.hpp ./gecode/kernel/data/shared-array.hpp ./gecode/kernel/data/shared-data.hpp \ + ./gecode/kernel/exception.hpp ./gecode/kernel/gpi.hpp ./gecode/kernel/macros.hpp \ + ./gecode/kernel/memory/allocators.hpp ./gecode/kernel/memory/config.hpp ./gecode/kernel/memory/manager.hpp \ + ./gecode/kernel/memory/region.hpp ./gecode/kernel/modevent.hpp ./gecode/kernel/propagator/advisor.hpp \ + ./gecode/kernel/propagator/pattern.hpp ./gecode/kernel/propagator/subscribed.hpp ./gecode/kernel/propagator/wait.hpp \ + ./gecode/kernel/range-list.hpp ./gecode/kernel/shared-object.hpp ./gecode/kernel/shared-space-data.hpp \ + ./gecode/kernel/trace/filter.hpp ./gecode/kernel/trace/general.hpp ./gecode/kernel/trace/print.hpp \ + ./gecode/kernel/trace/recorder.hpp ./gecode/kernel/trace/tracer.hpp ./gecode/kernel/trace/traits.hpp \ + ./gecode/kernel/var-imp.hpp ./gecode/kernel/var-type.hpp ./gecode/kernel/var.hpp \ + ./gecode/kernel/view.hpp ./gecode/search.hh ./gecode/search/bab.hpp \ + ./gecode/search/base.hpp ./gecode/search/build.hpp ./gecode/search/cutoff.hpp \ + ./gecode/search/dfs.hpp ./gecode/search/engine.hpp ./gecode/search/exception.hpp \ + ./gecode/search/lds.hpp ./gecode/search/options.hpp ./gecode/search/pbs.hpp \ + ./gecode/search/rbs.hpp ./gecode/search/sebs.hpp ./gecode/search/seq/dead.hh \ + ./gecode/search/statistics.hpp ./gecode/search/stop.hpp ./gecode/search/support.hh \ + ./gecode/search/trace-recorder.hpp ./gecode/search/tracer.hpp ./gecode/search/traits.hpp \ + ./gecode/support.hh ./gecode/support/allocator.hpp ./gecode/support/auto-link.hpp \ + ./gecode/support/bitset-base.hpp ./gecode/support/bitset-offset.hpp ./gecode/support/bitset.hpp \ + ./gecode/support/block-allocator.hpp ./gecode/support/cast.hpp gecode/support/config.hpp \ + ./gecode/support/dynamic-array.hpp ./gecode/support/dynamic-queue.hpp ./gecode/support/dynamic-stack.hpp \ + ./gecode/support/exception.hpp ./gecode/support/failpoint.hpp ./gecode/support/hash.hpp \ + ./gecode/support/heap.hpp ./gecode/support/hw-rnd.hpp ./gecode/support/int-type.hpp \ + ./gecode/support/macros.hpp ./gecode/support/marked-pointer.hpp ./gecode/support/random.hpp \ + ./gecode/support/ref-count.hpp ./gecode/support/run-jobs.hpp ./gecode/support/sort.hpp \ + ./gecode/support/static-stack.hpp ./gecode/support/thread.hpp ./gecode/support/thread/thread.hpp \ + ./gecode/support/timer.hpp ./test/int.hh ./test/int.hpp \ + ./test/test.hh ./test/test.hpp diff --git a/Makefile.in b/Makefile.in index 62bd48f92c..26d8c94e77 100755 --- a/Makefile.in +++ b/Makefile.in @@ -338,7 +338,7 @@ INTSRC0 = \ order.cpp order/propagate.cpp \ unary.cpp cumulative.cpp cumulatives.cpp \ circuit.cpp no-overlap.cpp nvalues.cpp \ - member.cpp branch/action.cpp branch/chb.cpp \ + member.cpp min-distance.cpp branch/action.cpp branch/chb.cpp \ arithmetic/mult.cpp \ branch/view-sel.cpp branch/val-sel-commit.cpp \ branch/view-values.cpp \ @@ -398,7 +398,8 @@ INTHDR0 = \ view/offset.hpp view/rel-test.hpp view/scale.hpp view/iter.hpp \ arithmetic.hh array.hpp array-traits.hpp bool.hh branch.hh \ distinct.hh dom.hh \ - element.hh exception.hpp gcc.hh linear.hh \ + element.hh min-distance.hh min-distance.hpp min-distance/prop.hpp \ + exception.hpp gcc.hh linear.hh \ reify.hpp propagator.hpp extensional.hh rel.hh \ sorted.hh var-imp.hpp view.hpp ipl.hpp irt.hpp \ channel.hh channel.hpp channel/dom.hpp channel/val.hpp \ @@ -1121,7 +1122,7 @@ INTTESTSRC00 = \ rel linear gcc sorted unshare exec sequence \ mm-arithmetic mm-bool mm-lin mm-count mm-rel \ bin-packing order unary cumulative cumulatives circuit \ - no-overlap precede nvalues member + no-overlap precede nvalues member min-distance INTTESTSRC0 = test/int.cpp $(INTTESTSRC00:%=test/int/%.cpp) INTTESTOBJ = $(INTTESTSRC0:%.cpp=%$(OBJSUFFIX)) diff --git a/changelog.in b/changelog.in index f66ceaa446..219972aeb1 100755 --- a/changelog.in +++ b/changelog.in @@ -149,6 +149,19 @@ to register and run Gecode-style tests for every configured variable family. Support shared and static harness libraries using the configured Gecode library variants. Add exact replay of a failed test iteration with `-replay`. +[ENTRY] +Module: int +What: new +Rank: major +[DESCRIPTION] +Add exact minimum-distance constraints over indexed sites, with decomposed +and single-propagator forward-bound propagation, optional conflict-matching +upper bounds, and inclusive requirements for selected-position pairs. +Distances can use a shared matrix or a shared callable evaluated on demand. +The propagation algorithms follow: +M. Z. Lagerkvist, Propagation Algorithms for the Minimum-Distance Constraint +over Selected Points, ModRef 2026. + [RELEASE] Version: 6.4.0 Date: 2026-07-15 diff --git a/cmake/GecodeSources.cmake b/cmake/GecodeSources.cmake index eb013c2682..09b4365587 100644 --- a/cmake/GecodeSources.cmake +++ b/cmake/GecodeSources.cmake @@ -96,6 +96,7 @@ set(GECODE_INT_SOURCES gecode/int/linear/bool-post.cpp gecode/int/linear/int-post.cpp gecode/int/member.cpp + gecode/int/min-distance.cpp gecode/int/no-overlap.cpp gecode/int/nvalues.cpp gecode/int/order.cpp @@ -416,6 +417,7 @@ set(GECODE_TEST_SOURCES test/int/gcc.cpp test/int/linear.cpp test/int/member.cpp + test/int/min-distance.cpp test/int/mm-arithmetic.cpp test/int/mm-bool.cpp test/int/mm-count.cpp diff --git a/gecode/int.hh b/gecode/int.hh index 9166211d6d..7d0e765955 100755 --- a/gecode/int.hh +++ b/gecode/int.hh @@ -2898,6 +2898,122 @@ namespace Gecode { #include +namespace Gecode { + + + /** + * \defgroup TaskModelIntMinDistance Minimum distance constraints + * \ingroup TaskModelInt + */ + //@{ + /** \brief Shared distances between indexed sites + * + * Distances are nonnegative, symmetric, and zero on the diagonal. + * No triangle inequality is required. Site indices range from zero to + * size()-1. Matrix entries or a callable are shared across propagators, + * clones, and search threads. + */ + class IntDistance : public SharedHandle { + protected: + /// Shared matrix or callable + class Data : public SharedHandle::Object { + public: + const int n; ///< Number of sites + const IntSharedArray matrix; ///< Row-major matrix, empty for a callable + const std::function function; ///< Computed distance + Data(int n, const IntArgs& matrix); + Data(int n, std::function function); + virtual ~Data(void); + }; + public: + /** \brief Copy and validate an \a n by \a n row-major distance matrix + * + * Throws Int::OutOfLimits for invalid sizes or distances, + * Int::ArgumentSizeMismatch for a wrong number of entries, and + * Int::IllegalOperation for asymmetry or a nonzero diagonal. + */ + GECODE_INT_EXPORT IntDistance(int n, const IntArgs& matrix); + /** \brief Compute distances on demand for \a n sites + * + * The callable must return a stable, symmetric, nonnegative distance + * within Int::Limits, and zero for equal indices. It must own or share + * its captured data, be safe for concurrent calls, and not depend on a + * Space. Construction does not evaluate it or materialize a matrix. + * Results are checked for range and a zero diagonal when evaluated; + * symmetry is the caller's responsibility. + * Throws Int::OutOfLimits for an invalid site count and InvalidFunction + * for an empty callable. + */ + GECODE_INT_EXPORT IntDistance(int n, std::function function); + /// Number of sites + int size(void) const; + /// Distance between valid indices \a a and \a b + int operator ()(int a, int b) const; + }; + + /// Organization of forward-bound minimum-distance propagation + enum class MinDistancePropKind { + MDP_DECOMPOSED, ///< One propagator per selected-position pair + MDP_SINGLE ///< One propagator with advisors and cached pair maxima + }; + /// One propagator per selected-position pair + static constexpr MinDistancePropKind MDP_DECOMPOSED = + MinDistancePropKind::MDP_DECOMPOSED; + /// One propagator with advisors and cached pair maxima + static constexpr MinDistancePropKind MDP_SINGLE = + MinDistancePropKind::MDP_SINGLE; + + /** \brief Post \f$z=\min_{i + namespace Gecode { /** diff --git a/gecode/int/min-distance.cpp b/gecode/int/min-distance.cpp new file mode 100644 index 0000000000..cfe9bf549d --- /dev/null +++ b/gecode/int/min-distance.cpp @@ -0,0 +1,143 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#include + +#include + +namespace Gecode { + + IntDistance::Data::Data(int n0, const IntArgs& m) + : n(n0), matrix(m) {} + + IntDistance::Data::Data(int n0, std::function f) + : n(n0), function(std::move(f)) {} + + IntDistance::Data::~Data(void) {} + + IntDistance::IntDistance(int n, const IntArgs& matrix) { + Int::Limits::positive(n,"IntDistance"); + if (static_cast(n)*n != matrix.size()) + throw Int::ArgumentSizeMismatch("IntDistance"); + for (int a=0; a f) { + Int::Limits::positive(n,"IntDistance"); + if (!f) + throw InvalidFunction("IntDistance"); + object(new Data(n,std::move(f))); + } + +} + +namespace Gecode { namespace Int { namespace MinDistance { + + /// Validate the organization and number of positions + void check(const IntVarArgs& x, MinDistancePropKind kind) { + if (x.size() < 2) + throw TooFewArguments("Int::minimum_distance"); + if ((kind != MDP_DECOMPOSED) && (kind != MDP_SINGLE)) + throw IllegalOperation("Int::minimum_distance"); + Limits::nonnegative(static_cast(x.size())*(x.size()-1)/2, + "Int::minimum_distance"); + } + + /// Shared posting for the two public overloads + ExecStatus post(Home home, const IntVarArgs& x, IntView z, + const IntDistance& d, const IntSharedArray& r, + MinDistancePropKind kind, IntPropLevel ipl) { + ViewArray views(home,x); + for (int i=0; i(x.size())*x.size() != r.size()) + throw Int::ArgumentSizeMismatch("Int::minimum_distance"); + for (int i=0; i + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GECODE_INT_MIN_DISTANCE_HH +#define GECODE_INT_MIN_DISTANCE_HH + +#include + +#include + +/** + * \namespace Gecode::Int::MinDistance + * + * The forward-bound and greedy conflict-matching algorithms follow: + * M. Z. Lagerkvist, Propagation Algorithms for the Minimum-Distance + * Constraint over Selected Points, ModRef 2026. + * https://github.com/zayenz/modref2026-minimum-distance-propagators + * + * \brief Minimum-distance propagators over indexed sites + */ +namespace Gecode { namespace Int { namespace MinDistance { + + /// Pair maximum and its witness + struct PairCache { + int a; ///< Site at the first endpoint + int b; ///< Site at the second endpoint + int upper; ///< Maximum distance between the endpoint domains + }; + + /** + * \brief Forward-bound actor for one selected-position pair + * + * Requires \code #include \endcode + * \ingroup FuncIntProp + */ + class Pair + : public MixTernaryPropagator { + protected: + /// Shared distances + IntDistance d; + /// Inclusive pair requirement + int r; + /// Constructor for posting + Pair(Home home, IntView x0, IntView y0, IntView z0, + const IntDistance& d0, int r0); + /// Constructor for cloning + Pair(Space& home, Pair& p); + public: + /// Post propagator + static ExecStatus post(Home home, IntView x0, IntView x1, IntView x2, + const IntDistance& d, int r); + /// Copy propagator during cloning + virtual Actor* copy(Space& home); + /// Cost function + virtual PropCost cost(const Space&, const ModEventDelta&) const; + /// Perform propagation + virtual ExecStatus propagate(Space& home, const ModEventDelta&); + /// Cancel subscriptions and dispose shared data + virtual size_t dispose(Space& home); + }; + + /** + * \brief Equality finalizer and zero witness for the pairwise organization + * + * Requires \code #include \endcode + * \ingroup FuncIntProp + */ + class Finalize + : public MixNaryOnePropagator { + protected: + /// Shared distances + IntDistance d; + /// Shared pair requirements + IntSharedArray r; + /// Constructor for posting + Finalize(Home home, ViewArray& x, IntView z, + const IntDistance& d0, const IntSharedArray& r0); + /// Constructor for cloning + Finalize(Space& home, Finalize& p); + public: + /// Post propagator + static ExecStatus post(Home home, ViewArray& x, IntView z, + const IntDistance& d, const IntSharedArray& r); + /// Copy propagator during cloning + virtual Actor* copy(Space& home); + /// Cost function + virtual PropCost cost(const Space&, const ModEventDelta&) const; + /// Perform propagation + virtual ExecStatus propagate(Space& home, const ModEventDelta&); + /// Cancel subscriptions and dispose shared data + virtual size_t dispose(Space& home); + }; + + /** + * \brief Single forward-bound actor with domain advisors and pair witnesses + * + * Requires \code #include \endcode + * \ingroup FuncIntProp + */ + class AdvisorForward : public Propagator { + protected: + /// Domain advisor for a selected position + class XAdvisor : public Advisor { + public: + /// Position in the view array + int index; + /// Constructor for creation + XAdvisor(Space& home, Propagator& p, Council& c, int i); + /// Constructor for cloning + XAdvisor(Space& home, XAdvisor& a); + }; + /// Selected sites + ViewArray x; + /// Minimum distance + IntView z; + /// Shared distances + IntDistance d; + /// Shared pair requirements + IntSharedArray r; + /// Domain advisors + Council council; + /// Pair maxima and witnesses + PairCache* cache; + /// Positions changed since the last fixpoint + Support::BitSetBase dirty; + /// Objective lower bound at the last fixpoint + int last_min; + /// Whether the objective shares a selected-site variable + bool z_shared; + /// Constructor for posting + AdvisorForward(Home home, ViewArray& x0, IntView z0, + const IntDistance& d0, const IntSharedArray& r0); + /// Constructor for cloning + AdvisorForward(Space& home, AdvisorForward& p); + public: + /// Post propagator + static ExecStatus post(Home home, ViewArray& x, IntView z, + const IntDistance& d, const IntSharedArray& r); + /// Copy propagator during cloning + virtual Actor* copy(Space& home); + /// Cost function + virtual PropCost cost(const Space&, const ModEventDelta&) const; + /// Reschedule propagation + virtual void reschedule(Space& home); + /// Record a changed selected-site domain + virtual ExecStatus advise(Space&, Advisor& a, const Delta&); + /// Perform propagation + virtual ExecStatus propagate(Space& home, const ModEventDelta&); + /// Cancel subscriptions and dispose stored data + virtual size_t dispose(Space& home); + }; + + /** + * \brief Independent matching-based objective upper bound + * + * Requires \code #include \endcode + * \ingroup FuncIntProp + */ + class Matching + : public MixNaryOnePropagator { + protected: + /// Shared distances + IntDistance d; + /// Constructor for posting + Matching(Home home, ViewArray& x, IntView z, + const IntDistance& d0); + /// Constructor for cloning + Matching(Space& home, Matching& p); + /// Whether a greedy conflict matching refutes the threshold + bool refuted(int* sites, int n, bool* matched, int threshold) const; + public: + /// Post propagator + static ExecStatus post(Home home, ViewArray& x, IntView z, + const IntDistance& d); + /// Copy propagator during cloning + virtual Actor* copy(Space& home); + /// Cost function + virtual PropCost cost(const Space&, const ModEventDelta&) const; + /// Perform propagation + virtual ExecStatus propagate(Space& home, const ModEventDelta&); + /// Cancel subscriptions and dispose shared data + virtual size_t dispose(Space& home); + }; + +}}} + +#include + +#endif + +// STATISTICS: int-prop diff --git a/gecode/int/min-distance.hpp b/gecode/int/min-distance.hpp new file mode 100644 index 0000000000..3ddea29655 --- /dev/null +++ b/gecode/int/min-distance.hpp @@ -0,0 +1,62 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + +#ifndef GECODE_INT_MIN_DISTANCE_HPP +#define GECODE_INT_MIN_DISTANCE_HPP + +namespace Gecode { + + forceinline int + IntDistance::size(void) const { + return static_cast(object())->n; + } + + forceinline int + IntDistance::operator ()(int a, int b) const { + const Data& d = *static_cast(object()); + assert((a >= 0) && (a < d.n) && (b >= 0) && (b < d.n)); + if (d.matrix) + return d.matrix[a*d.n+b]; + int v = d.function(a,b); + Int::Limits::nonnegative(v,"IntDistance::operator()"); + if ((a == b) && (v != 0)) + throw Int::IllegalOperation("IntDistance::operator()"); + return v; + } + +} + +#endif + +// STATISTICS: int-post diff --git a/gecode/int/min-distance/prop.hpp b/gecode/int/min-distance/prop.hpp new file mode 100644 index 0000000000..fa3048bf22 --- /dev/null +++ b/gecode/int/min-distance/prop.hpp @@ -0,0 +1,519 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + +namespace Gecode { namespace Int { namespace MinDistance { + + + /// Inclusive requirement, with an empty matrix denoting all zeroes + forceinline int + requirement(const IntSharedArray& r, int n, int i, int j) { + return !r ? 0 : r[i*n+j]; + } + + /// Prune the partner of an assigned endpoint + inline ExecStatus + forward(Space& home, IntView a, IntView b, int threshold, + const IntDistance& d, bool& changed) { + Region region; + int* remove = region.alloc(b.size()); + int nr = 0; + for (ViewValues v(b); v(); ++v) + if (d(a.val(),v.val()) < threshold) + remove[nr++] = v.val(); + if (nr != 0) { + Iter::Values::Array values(remove,static_cast(nr)); + ModEvent me = b.minus_v(home,values,false); + GECODE_ME_CHECK(me); + changed |= me_modified(me); + } + return ES_OK; + } + + /// Recompute the maximum distance between current domains + inline void + pair_max(IntView x, IntView y, const IntDistance& d, PairCache& c) { + c.upper = -1; + for (ViewValues a(x); a(); ++a) + for (ViewValues b(y); b(); ++b) { + int v = d(a.val(),b.val()); + if (v > c.upper) { + c.a = a.val(); c.b = b.val(); c.upper = v; + } + } + } + + /// Validate and bind a complete assignment + inline ExecStatus + assigned(Space& home, const ViewArray& x, IntView z, + const IntDistance& d, const IntSharedArray& r) { + int minimum = Limits::max; + for (int i=0; i& x, const IntDistance& d, + const IntSharedArray& r, bool& changed, bool& realized) { + int count = 0; + int first = -1; + int second = -1; + for (int i=0; i a(x[i]); a() && !supported; ++a) + for (ViewValues b(x[j]); b(); ++b) + if (d(a.val(),b.val()) == 0) { + supported = true; + break; + } + if (supported) { + first = i; second = j; count++; + } + } + if (count == 0) + return ES_FAILED; + if (count == 1) { + // Only this pair can attain zero, so both endpoints need zero support. + Region region; + for (int pass=0; pass<2; pass++) { + IntView a = x[pass == 0 ? first : second]; + IntView b = x[pass == 0 ? second : first]; + int* remove = region.alloc(a.size()); + int nr = 0; + for (ViewValues v(a); v(); ++v) { + bool supported = false; + for (ViewValues w(b); w(); ++w) + if (d(v.val(),w.val()) == 0) { + supported = true; + break; + } + if (!supported) + remove[nr++] = v.val(); + } + if (nr != 0) { + Iter::Values::Array values(remove,static_cast(nr)); + ModEvent me = a.minus_v(home,values,false); + GECODE_ME_CHECK(me); + changed |= me_modified(me); + } + } + } + return ES_OK; + } + + forceinline + Pair::Pair(Home home, IntView x0, IntView y0, IntView z0, + const IntDistance& d0, int r0) + : MixTernaryPropagator(home,x0,y0,z0), + d(d0), r(r0) { + home.notice(*this,AP_DISPOSE); + } + + forceinline + Pair::Pair(Space& home, Pair& p) + : MixTernaryPropagator(home,p), + d(p.d), r(p.r) {} + + inline ExecStatus + Pair::post(Home home, IntView x0, IntView x1, IntView x2, + const IntDistance& d, int r) { + if (x0.assigned() && x1.assigned()) { + int v = d(x0.val(),x1.val()); + if (v < r) + return ES_FAILED; + GECODE_ME_CHECK(x2.lq(home,v)); + } else { + (void) new (home) Pair(home,x0,x1,x2,d,r); + } + return ES_OK; + } + + inline Actor* + Pair::copy(Space& home) { + return new (home) Pair(home,*this); + } + + inline PropCost + Pair::cost(const Space&, const ModEventDelta&) const { + return PropCost::quadratic(PropCost::HI,d.size()); + } + + inline ExecStatus + Pair::propagate(Space& home, const ModEventDelta&) { + int threshold = std::max(r,x2.min()); + if (x0.assigned() && x1.assigned()) { + int v = d(x0.val(),x1.val()); + if (v < threshold) + return ES_FAILED; + GECODE_ME_CHECK(x2.lq(home,v)); + return home.ES_SUBSUMED(*this); + } + bool changed = false; + if (x0.assigned()) { + GECODE_ES_CHECK(forward(home,x0,x1,threshold,d,changed)); + } else if (x1.assigned()) { + GECODE_ES_CHECK(forward(home,x1,x0,threshold,d,changed)); + } + PairCache c; + pair_max(x0,x1,d,c); + if (c.upper < threshold) + return ES_FAILED; + ModEvent me = x2.lq(home,c.upper); + GECODE_ME_CHECK(me); + return (changed || me_modified(me) || (x2.min() > threshold)) ? ES_NOFIX : ES_FIX; + } + + inline size_t + Pair::dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); + d.~IntDistance(); + (void) MixTernaryPropagator::dispose(home); + return sizeof(*this); + } + + forceinline + Finalize::Finalize(Home home, ViewArray& x, IntView z, + const IntDistance& d0, const IntSharedArray& r0) + : MixNaryOnePropagator(home,x,z), + d(d0), r(r0) { + home.notice(*this,AP_DISPOSE); + } + + forceinline + Finalize::Finalize(Space& home, Finalize& p) + : MixNaryOnePropagator(home,p), + d(p.d), r(p.r) {} + + inline ExecStatus + Finalize::post(Home home, ViewArray& x, IntView z, + const IntDistance& d, const IntSharedArray& r) { + (void) new (home) Finalize(home,x,z,d,r); + return ES_OK; + } + + inline Actor* + Finalize::copy(Space& home) { + return new (home) Finalize(home,*this); + } + + inline PropCost + Finalize::cost(const Space&, const ModEventDelta&) const { + return PropCost::quadratic(PropCost::HI,x.size()); + } + + inline ExecStatus + Finalize::propagate(Space& home, const ModEventDelta&) { + if (x.assigned()) { + GECODE_ES_CHECK(assigned(home,x,y,d,r)); + return home.ES_SUBSUMED(*this); + } + if (y.max() != 0) + return ES_FIX; + bool changed = false; + bool realized = false; + GECODE_ES_CHECK(zero(home,x,d,r,changed,realized)); + if (realized) + return home.ES_SUBSUMED(*this); + return changed ? ES_NOFIX : ES_FIX; + } + + inline size_t + Finalize::dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); + d.~IntDistance(); r.~IntSharedArray(); + (void) MixNaryOnePropagator::dispose(home); + return sizeof(*this); + } + + forceinline + AdvisorForward::XAdvisor::XAdvisor(Space& home, Propagator& p, + Council& c, int i) + : Advisor(home,p,c), index(i) {} + + forceinline + AdvisorForward::XAdvisor::XAdvisor(Space& home, XAdvisor& a) + : Advisor(home,a), index(a.index) {} + + forceinline + AdvisorForward::AdvisorForward(Home home, ViewArray& x0, IntView z0, + const IntDistance& d0, const IntSharedArray& r0) + : Propagator(home), x(x0), z(z0), d(d0), r(r0), council(home), + dirty(static_cast(home),static_cast(x.size()),true), + last_min(-1), z_shared(false) { + home.notice(*this,AP_DISPOSE); + Space& s = home; + int np = static_cast(static_cast(x.size())*(x.size()-1)/2); + cache = s.alloc(np); + for (int i=0; i(static_cast(x.size())*(x.size()-1)/2); + cache = home.alloc(np); + std::copy(p.cache,p.cache+np,cache); + } + + inline ExecStatus + AdvisorForward::post(Home home, ViewArray& x, IntView z, + const IntDistance& d, const IntSharedArray& r) { + (void) new (home) AdvisorForward(home,x,z,d,r); + return ES_OK; + } + + inline Actor* + AdvisorForward::copy(Space& home) { + return new (home) AdvisorForward(home,*this); + } + + inline PropCost + AdvisorForward::cost(const Space&, const ModEventDelta&) const { + return PropCost::quadratic(PropCost::HI, + static_cast(x.size())+d.size()); + } + + forceinline void + AdvisorForward::reschedule(Space& home) { + z.reschedule(home,*this,PC_INT_BND); + // Advisors have no reschedule method; ensure a manual reschedule also + // revisits every pair, even if no new domain event has been delivered. + last_min = -1; + } + + inline ExecStatus + AdvisorForward::advise(Space&, Advisor& a, const Delta&) { + dirty.set(static_cast(a).index); + return ES_NOFIX; + } + + inline ExecStatus + AdvisorForward::propagate(Space& home, const ModEventDelta&) { + for (;;) { + if (x.assigned()) { + GECODE_ES_CHECK(assigned(home,x,z,d,r)); + return home.ES_SUBSUMED(*this); + } + int zmin = z.min(); + bool all_dirty = zmin != last_min; + bool changed = false; + for (int i=0; i a(council); a(); ++a) + x[a.advisor().index].cancel(home,a.advisor()); + council.dispose(home); + z.cancel(home,*this,PC_INT_BND); + int np = static_cast( + static_cast(x.size())*(x.size()-1)/2); + home.free(cache,np); + dirty.dispose(home); + d.~IntDistance(); r.~IntSharedArray(); + (void) Propagator::dispose(home); + return sizeof(*this); + } + + forceinline + Matching::Matching(Home home, ViewArray& x, IntView z, + const IntDistance& d0) + : MixNaryOnePropagator(home,x,z), d(d0) { + home.notice(*this,AP_DISPOSE); + home.notice(*this,AP_WEAKLY); + } + + forceinline + Matching::Matching(Space& home, Matching& p) + : MixNaryOnePropagator(home,p), d(p.d) {} + + inline bool + Matching::refuted(int* sites, int n, bool* matched, int threshold) const { + if (threshold == 0) + return false; // Repeated sites are permitted at distance zero. + std::fill(matched,matched+n,false); + int edges = 0; + for (int a=0; a& x, IntView z, + const IntDistance& d) { + if (!z.assigned()) + (void) new (home) Matching(home,x,z,d); + return ES_OK; + } + + inline Actor* + Matching::copy(Space& home) { + return new (home) Matching(home,*this); + } + + inline PropCost + Matching::cost(const Space&, const ModEventDelta&) const { + return PropCost::cubic(PropCost::HI,d.size()); + } + + inline ExecStatus + Matching::propagate(Space& home, const ModEventDelta&) { + if (y.assigned()) + return home.ES_SUBSUMED(*this); + Region region; + bool* present = region.alloc(d.size()); + std::fill(present,present+d.size(),false); + for (int i=0; i v(x[i]); v(); ++v) + present[v.val()] = true; + int* sites = region.alloc(d.size()); + int n = 0; + for (int i=0; i(n); + int lo = y.min(); + int hi = y.max(); + if (refuted(sites,n,matched,lo)) + return ES_FAILED; + // A failed certificate at t excludes every objective >= t. Greedy + // certificate sizes themselves need not be monotone, so this search + // can miss tighter bounds but cannot delete a feasible objective. + while (lo < hi) { + int mid = lo+(hi-lo+1)/2; + if (refuted(sites,n,matched,mid)) + hi = mid-1; + else + lo = mid; + } + ModEvent me = y.lq(home,hi); + GECODE_ME_CHECK(me); + if (y.assigned()) + return home.ES_SUBSUMED(*this); + return me_modified(me) ? ES_NOFIX : ES_FIX; + } + + inline size_t + Matching::dispose(Space& home) { + home.ignore(*this,AP_DISPOSE); + home.ignore(*this,AP_WEAKLY); + d.~IntDistance(); + (void) MixNaryOnePropagator::dispose(home); + return sizeof(*this); + } + +}}} + +// STATISTICS: int-prop diff --git a/test/int/min-distance.cpp b/test/int/min-distance.cpp new file mode 100644 index 0000000000..d993efc72a --- /dev/null +++ b/test/int/min-distance.cpp @@ -0,0 +1,281 @@ +/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ +/* + * Main authors: + * Mikael Zayenz Lagerkvist + * + * Copyright: + * Mikael Zayenz Lagerkvist, 2026 + * + * This file is part of Gecode, the generic constraint + * development environment: + * http://www.gecode.dev + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ + + + +#include "test/int.hh" + +#include + +namespace Test { namespace Int { namespace MinDistance { + + /// Distances with both a zero off-diagonal pair and threshold boundaries + Gecode::IntArgs matrix(void) { + return Gecode::IntArgs({0,0,3,4, 0,0,2,1, 3,2,0,4, 4,1,4,0}); + } + + /// Exhaustive assignments and incremental clone/prune checks + class Minimum : public Test { + protected: + int positions; + bool computed; + bool requirements; + int alias; + Gecode::MinDistancePropKind kind; + public: + Minimum(int n, bool f, bool r, int a, + Gecode::MinDistancePropKind k, Gecode::IntPropLevel level) + : Test(TestTags::check(), + "MinDistance::"+str(n)+"::"+str(f)+"::"+str(r)+"::"+ + str(a)+"::"+str(k == Gecode::MDP_SINGLE)+"::"+str(level), + n+1,0,4,false,level), + positions(n), computed(f), requirements(r), alias(a), kind(k) { + contest = CTL_NONE; + } + virtual bool solution(const Assignment& a) const { + int minimum = 4; + Gecode::IntArgs m = matrix(); + for (int i=0; i= 4) + return false; + for (int j=i+1; j= 4) + return false; + int first = a[alias == 1 && i == 1 ? 0 : i]; + int second = a[alias == 1 && j == 1 ? 0 : j]; + int distance = m[first*4+second]; + if (requirements && (i == 0) && (j == positions-1) && + (distance < 3)) + return false; + minimum = std::min(minimum,distance); + } + } + return (a[positions] == minimum) && + ((alias != 2) || (a[positions] == a[0])); + } + virtual void post(Gecode::Space& home, Gecode::IntVarArray& a) { + using namespace Gecode; + IntArgs m = matrix(); + IntDistance d = computed ? + IntDistance(4,[m](int i, int j) { return m[i*4+j]; }) : + IntDistance(4,m); + IntVarArgs x(positions); + for (int i=0; i distance) + : x(*this,n,0,sites-1), z(*this,lower,upper) { + Gecode::minimum_distance(*this,x,z, + Gecode::IntDistance(sites,distance),level,kind); + } + Model(Model& s) : Gecode::Space(s) { + x.update(*this,s.x); z.update(*this,s.z); + } + virtual Gecode::Space* copy(void) { return new Model(*this); } + }; + + /// Matching certificates, zero witnesses, and callback lifetime + class Propagation : public Base { + public: + Propagation(void) : Base("Int::MinDistance::Propagation", + TestTags::check()) {} + virtual bool run(void) { + using namespace Gecode; + const MinDistancePropKind kinds[] = {MDP_DECOMPOSED,MDP_SINGLE}; + const IntPropLevel levels[] = {IPL_BASIC,IPL_ADVANCED}; + for (MinDistancePropKind kind : kinds) + for (IntPropLevel level : levels) { + // Forward filtering includes equality at the threshold. + Model forward(2,4,2,4,kind,level, + [](int a, int b) { return std::abs(a-b); }); + rel(forward,forward.x[0],IRT_EQ,0); + if ((forward.status() == SS_FAILED) || + (forward.x[1].min() != 2) || (forward.z.max() != 3)) + return false; + std::unique_ptr clone(static_cast(forward.clone())); + rel(*clone,clone->x[1],IRT_EQ,2); + if ((clone->status() == SS_FAILED) || !clone->z.assigned() || + (clone->z.val() != 2) || forward.z.assigned()) + return false; + + // With only one pair, zero requires equal selections here. + Model zero(2,4,0,0,kind,level, + [](int a, int b) { return std::abs(a-b); }); + dom(zero,zero.x[0],IntSet({0,1})); + dom(zero,zero.x[1],IntSet({1,2})); + if ((zero.status() == SS_FAILED) || + !zero.x[0].assigned() || (zero.x[0].val() != 1) || + !zero.x[1].assigned() || (zero.x[1].val() != 1)) + return false; + + // Assigned objectives still need exact equality, not just >= z. + Model invalid(2,4,1,1,kind,level, + [](int a, int b) { return std::abs(a-b); }); + rel(invalid,invalid.x[0],IRT_EQ,0); + rel(invalid,invalid.x[1],IRT_EQ,3); + if (invalid.status() != SS_FAILED) + return false; + + // One active site can be repeated at objective zero. + Model repeated(3,1,0,3,kind,level, + [](int, int) { return 0; }); + if ((repeated.status() == SS_FAILED) || !repeated.z.assigned() || + (repeated.z.val() != 0)) + return false; + } + + // A perfect conflict matching rules out selecting three sites at 3. + auto conflicts = [](int a, int b) { + return a == b ? 0 : (a/2 == b/2 ? 1 : 3); + }; + for (MinDistancePropKind kind : kinds) { + Model basic(3,4,1,3,kind,IPL_BASIC,conflicts); + Model matching(3,4,1,3,kind,IPL_ADVANCED,conflicts); + if ((basic.status() == SS_FAILED) || (basic.z.max() != 3) || + (matching.status() == SS_FAILED) || (matching.z.max() != 1)) + return false; + } + + // Share a captured object across stable clones and release it with + // the last Space. Computed distances must not build an n*n matrix. + std::weak_ptr lifetime; + std::unique_ptr clone; + { + auto data = std::make_shared(1); + lifetime = data; + Model original(2,1000000,1,3,MDP_SINGLE,IPL_BASIC, + [data](int a, int b) { return a == b ? 0 : *data; }); + // Restrict domains before propagation to avoid enumerating the + // million-site universe; construction itself makes no distance calls. + dom(original,original.x,0,2); + if (original.status() == SS_FAILED) + return false; + clone.reset(static_cast(original.clone())); + } + if (lifetime.expired() || (clone->status() == SS_FAILED)) + return false; + clone.reset(); + if (!lifetime.expired()) + return false; + return true; + } + }; + + /// Matrix and callback validation + class Arguments : public Base { + public: + Arguments(void) : Base("Int::MinDistance::Arguments", + TestTags::check()) {} + virtual bool run(void) { + using namespace Gecode; + try { + IntDistance invalid(2,IntArgs({0,1,2,0})); + return false; + } catch (const Gecode::Int::IllegalOperation&) {} + try { + IntDistance invalid(2,IntArgs({0,-1,-1,0})); + return false; + } catch (const Gecode::Int::OutOfLimits&) {} + try { + IntDistance invalid(2,std::function()); + return false; + } catch (const InvalidFunction&) {} + try { + IntDistance invalid(2,[](int, int) { return -1; }); + (void) invalid(0,1); + return false; + } catch (const Gecode::Int::OutOfLimits&) {} + try { + Model space(2,4,0,4,MDP_DECOMPOSED,IPL_BASIC, + [](int a, int b) { return std::abs(a-b); }); + IntVarArgs x; + IntVar z(space,0,4); + minimum_distance(space,x,z,IntDistance(4,matrix())); + return false; + } catch (const Gecode::Int::TooFewArguments&) {} + return true; + } + }; + + /// Register the compact semantic matrix + class Create { + public: + Create(void) { + const Gecode::MinDistancePropKind kinds[] = + {Gecode::MDP_DECOMPOSED,Gecode::MDP_SINGLE}; + const Gecode::IntPropLevel levels[] = + {Gecode::IPL_BASIC,Gecode::IPL_ADVANCED}; + for (int n : {2,3}) + for (bool computed : {false,true}) + for (bool requirements : {false,true}) + for (Gecode::MinDistancePropKind kind : kinds) + for (Gecode::IntPropLevel level : levels) + (void) new Minimum(n,computed,requirements,0,kind,level); + for (Gecode::MinDistancePropKind kind : kinds) + for (Gecode::IntPropLevel level : levels) + (void) new Minimum(3,true,true,1,kind,level); + for (Gecode::MinDistancePropKind kind : kinds) + for (Gecode::IntPropLevel level : levels) + (void) new Minimum(3,true,false,2,kind,level); + (void) new Propagation; + (void) new Arguments; + } + }; + Create create; + +}}} + +// STATISTICS: test-int