【ortools 源码系列10】模型求解cp-sat solve源码分析

ortools整数规划模型求解cp-sat solve源码分析


求解模型入口代码

CpSolverResponse Solve(const CpModelProto& model_proto) {
   
  Model model;
  return SolveCpModel(model_proto, &model);
}

1.class Model

功能

该源码为OR-Tools库中的Model类,用于拥有和管理一个优化模型。Model类提供了一些功能,包括:

  • 可以给模型命名,方便调试和记录。
  • 可以通过Add函数向模型添加约束,并支持返回约束对象的模板函数。
  • 可以通过GetOrCreate函数获取模型中的单例对象,并支持返回对象的可变和不可变版本。
  • 可以通过TakeOwnership函数将指针的所有权交给模型,由模型负责在销毁时释放内存。
  • 可以通过Create函数创建一个非单例对象,并自动将其所有权交给模型。
  • 可以通过Register函数注册一个非拥有的类作为模型中的单例对象。

总之,Model类为优化模型提供了管理和维护的功能,使得构建和操作优化模型更加方便和灵活。

用法

  • Model(): 默认构造函数,创建一个空的Model对象。用于初始化一个新的模型。
  • ~Model(): 析构函数,释放Model对象及其所有拥有的资源。在销毁Model对象时调用,确保释放所有的资源。
  • Model(std::string name): 带名称的构造函数,创建一个具有指定名称的Model对象。可用于给模型命名,方便调试和记录。
  • template <typename T> T Add(std::function<T(Model*)> f): 向模型中添加约束或其他元素的方法。接受一个函数对象作为参数,该函数使用模型作为参数,并返回所添加的约束或元素。可以通过lambda表达式或其他函数对象来创建并添加约束。
  • template <typename T> T Get(std::function<T(const Model&)> f) const: 获取模型中特定类型的对象的方法。接受一个函数对象作为参数,该函数使用模型的常量引用作为参数,并返回所需的对象。可以通过lambda表达式或其他函数对象来获取对象。
  • template <typename T> T* GetOrCreate(): 获取模型中唯一的对象,如果对象不存在则创建一个新对象。根据对象的类型,在模型中查找是否已经存在该类型的对象实例,如果存在则返回该实例,否则创建一个新对象,并将其添加到模型中。
  • template <typename T> const T* Get() const: 获取模型中特定类型的不可变对象的方法。根据对象的类型,在模型中查找是否存在该类型的对象实例,如果存在则返回该实例,否则返回空指针。
  • template <typename T> T* Mutable() const: 获取模型中特定类型的可变对象的方法。根据对象的类型,在模型中查找是否存在该类型的对象实例,如果存在则返回该实例,否则返回空指针。可以用于修改模型中对象的状态或属性。
  • template <typename T> T* TakeOwnership(T* t): 将指针的所有权交给模型,由模型在销毁时释放内存。将指定的指针添加到模型的资源管理列表中,以确保在模型销毁时正确释放内存。
  • template <typename T> T* Create(): 创建一个非单例对象,并将其所有权交给模型。通过调用T类的构造函数来创建一个新对象,并将其添加到模型的资源管理列表中,以确保在模型销毁时正确释放内存。
  • template <typename T> void Register(T* non_owned_class): 注册一个非拥有的类作为模型中的单例对象。将指定的非拥有类添加到模型的单例对象映射中,以便在需要时可以获取到该对象的唯一实例。

这些方法提供了对模型的管理和操作的功能。可以使用Add方法向模型中添加约束或其他元素,使用Get方法获取模型中特定类型的对象,使用GetOrCreate方法获取或创建模型中的唯一对象,使用Mutable方法获取模型中特定类型的可变对象,使用TakeOwnership方法将指针的所有权交给模型,使用Create方法创建一个非单例对象并将其所有权交给模型,使用Register方法注册一个非拥有的类作为模型中的单例对象。这些方法提供了方便和灵活的方式来构建、操作和管理优化模型。

中文源码


namespace operations_research {
   
namespace sat {
   

/**
 * 拥有与特定优化模型相关的所有内容的类。
 *
 * 实际上,这个类是一个完全通用的包装器,可以持有任何类型的约束、监视器、求解器,
 * 并提供将它们连接在一起的机制。
 */
class Model {
   
 public:
  Model() {
   }

  ~Model() {
   
    // 删除的顺序似乎依赖于平台。
    // 我们强制清理向量的顺序是相反的。
    for (int i = cleanup_list_.size() - 1; i >= 0; --i) {
   
      cleanup_list_[i].reset();
    }
  }

  /**
   * 当应用程序中有多个模型时,为了调试或记录,给它们命名是有意义的。
   */
  explicit Model(std::string name) : name_(name) {
   }

  /**
   * 这使得在客户端上有一个更好的API,并且允许两种形式:
   *   - ConstraintCreationFunction(constraint_args, &model);
   *   - model.Add(ConstraintCreationFunction(constraint_args));
   *
   * 第二种形式对客户端来说更好,而且还可以存储约束并以后添加它们。然而,创建约束的函数稍微复杂一些。
   *
   * \code
   std::function<void(Model*)> ConstraintCreationFunction(constraint_args) {
     return [=] (Model* model) {
        ... 相同的代码 ...
     };
   }
   \endcode
   *
   * 对于需要的函数,我们还有一个带模板返回值的版本,例如
   * \code
   const BooleanVariable b = model.Add(NewBooleanVariable());
   const IntegerVariable i = model.Add(NewWeightedSum(weights, variables));
   \endcode
   */
  template <typename T>
  T Add(std::function<T(Model*)> f) {
   
    return f(this);
  }

  /// 类似于Add(),但是这个是const的。
  template <typename T>
  T Get(std::function<T(const Model&)> f) const {
   
    return f(*this);
  }

  /**
   * 返回一个类型为T的对象,该对象对该模型是唯一的(类似于“本地”单例)。
   * 如果需要,使用T(Model* model)构造函数创建一个已经存在的实例,
   * 否则使用T()构造函数创建一个新实例。
   *
   * 这在依赖注入框架中的工作方式上有点像,并且可以非常容易地将组成求解器的所有类连接在一起。
   * 例如,一个约束可以依赖于LiteralTrail或IntegerTrail或两者,
   * 它可以依赖于Watcher类来注册自己以便在需要时调用等等。
   *
   * 重要提示:Model*构造函数之间不应形成循环,否则这将导致程序崩溃。
   */
  template <typename T>
  T* GetOrCreate() {
   
    const size_t type_id = gtl::FastTypeId<T>();
    auto find = singletons_.find(type_id);
    if (find != singletons_.end()) {
   
      return static_cast<T*>(find->second);
    }

    // 新元素。
    // 计划(user):直接在singletons_中存储std::unique_ptr<>?
    T* new_t = MyNew<T>(0);
    singletons_[type_id] = new_t;
    TakeOwnership(new_t);
    return new_t;
  }

  /**
   * 类似于GetOrCreate(),但是如果对象不存在,则不创建该对象。
   *
   * 返回对象的const版本。
   */
  template <typename T>
  const T* Get() const {
   
    const auto& it = singletons_.find(gtl::FastTypeId<T>());
    return it != singletons_.end() ? static_cast<const T*>(it->second)
                                   : nullptr;
  }

  /**
   * 与Get()相同,但返回对象的可变版本。
   */
  template <typename T>
  T* Mutable() const {
   
    const auto& it = singletons_.find(gtl::FastTypeId<T>());
    return it != singletons_.end() ? static_cast<T*>(it->second) : nullptr;
  }

  /**
   * 将指针所有权交给此模型。
   *
   * 当模型销毁时,它将被销毁。
   */
  template <typename T>
  T* TakeOwnership(T* t) {
   
    cleanup_list_.emplace_back(new Delete<T>(t));
    return t;
  }

  /**
   * 这返回一个由模型拥有且使用T(Model* model)构造函数创建的非单例对象,
   * 如果不存在则使用T()构造函数。这只是new + TakeOwnership()的快捷方式。
   */
  template <typename T>
  T* Create() {
   
    T* new_t = MyNew<T>(0);
    TakeOwnership(new_t);
    return new_t;
  }

  /**
   * 注册一个模型中将是“单例”的非拥有类。
   *
   * 在已经注册的类上调用此方法是错误的。
   */
  template <typename T>
  void Register(T* non_owned_class) {
   
    const size_t type_id = gtl::FastTypeId<T>();
    CHECK(!singletons_.contains(type_id));
    singletons_[type_id] = non_owned_class;
  }

  const std::string& Name() const {
    return name_; }

 private:
  // 如果在decltype()中的类型存在,则只定义第一个MyNew()。
  // 第二个MyNew()将始终被定义,但由于省略号的原因,它的优先级低于第一个MyNew()。
  template <typename T>
  decltype(T(static_cast<Model*>(nullptr)))* MyNew(int) {
   
    return new T(this);
  }
  template <typename T>
  T* MyNew(...) {
   
    return new T();
  }

  const std::string name_;

  // FastTypeId<T>到类型为T的“单例”的映射。
  absl::flat_hash_map</*typeid*/ size_t, void*> singletons_;

  struct DeleteInterface {
   
    virtual ~DeleteInterface() = default;
  };
  template <typename T>
  class Delete : public DeleteInterface {
   
   public:
    explicit Delete(T* t) : to_delete_(t) {
   }
    ~Delete() override = default;

   private:
    std::unique_ptr<T> to_delete_;
  };

  // 要删除的项目列表。
  //
  // 计划(user):我认为我们不需要两层unique_ptr,但是我们对效率并不太在意,
  // 并且这更容易工作。
  std::vector<std::unique_ptr<DeleteInterface>> cleanup_list_;

  DISALLOW_COPY_AND_ASSIGN(Model);
};

}  // namespace sat
}  // namespace operations_research

#endif  // OR_TOOLS_SAT_MODEL_H_

2.SolveCpModel函数

功能

这段代码是一个求解CP模型的函数。首先,它初始化了一些计时器,并检查是否需要输出初始模型和覆盖参数。

接下来,它启用了日志记录组件,并根据参数设置是否将日志记录到标准输出和响应中。

然后,它创建了一个共享的响应管理器,并设置了响应的转储前缀。

之后,它添加了一些后处理器和响应后处理器,用于在求解完成后执行一些操作,如转储响应到文件和记录时间信息。

接着,它验证了参数的有效性,如果参数无效,则返回一个包含错误信息的响应。

然后,它从参数中初始化了时间限制。

接下来,它注册了SIGINT信号处理程序,以便在收到SIGINT信号时停止求解。

然后,它输出一些求解器的信息和参数。

接着,它更新了参数中的工作线程数。

然后,它检查是否需要打印随机数生成器的salt。

接下来,它验证了模型的有效性,如果模型无效,则返回一个包含错误信息的响应。

最后,如果模型是纯SAT问题,则调用了另一个函数来求解纯SAT模型并返回结果。

整体来说,这段代码主要是对CP模型进行求解的一系列准备工作,包括参数设置、验证、日志记录等,最后调用函数求解模型并返回结果。

步骤

SolveCpModel函数是用于求解Constraint Programming模型的主要函数。它执行以下步骤:

  1. 检查是否需要将预处理后的模型转储为文本文件,并进行转储。
  2. 检查是否需要在预处理之后停止求解,或者已达到时间限制。如果是,则返回当前状态作为最终响应。
  3. 加载模型并初始化目标域和主积分。
  4. 如果指定了完整解提示,测试其可行性并加载。
  5. 根据参数设置,检测并添加对称性约束。
  6. 根据平台选择多线程求解或顺序求解。
    • 如果是多线程求解,调用SolveCpModelParallel函数进行并行求解。
    • 否则,使用顺序求解:
      • 创建一个局部模型,并将时间限制、SatParameters、统计信息和响应管理器注册到该模型中。
      • 加载模型和调试解。
      • 进行快速求解或最小化L1距离(根据参数设置)。
      • 调用SolveLoadedCpModel函数进行求解。
      • 填充响应中的统计信息。
      • 如果启用日志记录,顺序记录线性规划约束的统计信息。
  7. 如果启用日志记录,记录共享统计信息。
  8. 返回最终响应。

中文源码分析

CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) {
   
  auto* wall_timer = model->GetOrCreate<WallTimer>();
  auto* user_timer = model->GetOrCreate<UserTimer>();
  wall_timer->Start();
  user_timer->Start();

#if !defined(__PORTABLE_PLATFORM__)
#endif  // __PORTABLE_PLATFORM__

#if !defined(__PORTABLE_PLATFORM__)
  // 是否输出初始模型?
  if (absl::GetFlag(FLAGS_cp_model_dump_models)
  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值