/*
   Copyright (C) 2004 - 2025 by Guillaume Melquiond <guillaume.melquiond@inria.fr>
   Copyright (C) 2023 - 2024 by Tom Hubrecht <tom.hubrecht@ens.fr>
   Part of the Gappa tool https://gappa.gitlabpages.inria.fr/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the CeCILL Free Software License Agreement or
   under the terms of the GNU General Public License version.

   See the COPYING and COPYING.GPL files for more details.
*/

#include "numbers/interval.hpp"
#include "numbers/interval_arith.hpp"
#include "numbers/interval_utility.hpp"
#include "numbers/real.hpp"
#include "numbers/round.hpp"
#include "parser/ast.hpp"
#include "parser/ast_real.hpp"
#include "parser/pattern.hpp"
#include "proofs/proof_graph.hpp"
#include "proofs/schemes.hpp"

static preal_vect one_needed(ast_real const *r) {
  return preal_vect(1, predicated_real(r, PRED_BND));
}

static bool is_changing_sign(ast_real const *r)
{
  real_op const *p = boost::get< real_op const >(r);
  return p && (p->type == UOP_ABS || p->type == UOP_NEG);
}

bool is_hidden(ast_real const *r)
{
  return boost::get< hidden_real const >(r);
}

// LIN(a, b) <- REL(a, b)
REGISTER_SCHEME_BEGIN(lin_of_rel);
lin_of_rel_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "lin_of_rel") {}
REGISTER_SCHEME_END_PATTERN(lin_of_rel, predicated_real(pattern(0), pattern(1), PRED_LIN));

void lin_of_rel_scheme::compute(property const hyps[], property &res, std::string &) const {
  res.bnd() = hyps[0].bnd() + interval(1, 1);
}

proof_scheme *lin_of_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  if (!is_approx(a, b)) return NULL;

  preal_vect needed;
  needed.push_back(predicated_real(a, b, PRED_REL));
  return new lin_of_rel_scheme(real, needed);
}

// LIN(a - b, b) <- REL(a, b)
REGISTER_SCHEME_BEGIN(lin_of_rel2);
lin_of_rel2_scheme(predicated_real const &r, preal_vect const &v) :
  proof_scheme(r, v, "lin_of_rel2", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(lin_of_rel2, predicated_real(pattern(0) - pattern(1), pattern(1), PRED_LIN));

void lin_of_rel2_scheme::compute(property const hyps[], property &res, std::string &) const {
  res.bnd() = hyps[0].bnd();
}

proof_scheme *lin_of_rel2_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  if (!is_approx(a, b)) return NULL;

  preal_vect needed;
  needed.push_back(predicated_real(a, b, PRED_REL));
  return new lin_of_rel2_scheme(real, needed);
}

// FLT(a - b) <- FLT(a), FLT(b), LIN(a, b)
REGISTER_SCHEME_BEGIN(sub_flt_lin);
sub_flt_lin_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "sub_flt_lin") {}
REGISTER_SCHEME_END_PATTERN(sub_flt_lin, predicated_real(pattern(0) - pattern(1), PRED_FLT));

void sub_flt_lin_scheme::compute(property const hyps[], property &res, std::string &) const {
  static interval r(0.5, 2);
  if (hyps[2].bnd() <= r)
    res.cst() = std::max(hyps[0].cst(), hyps[1].cst());
  else res.clear();
}

proof_scheme *sub_flt_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];

  preal_vect needed;
  needed.push_back(predicated_real(a, PRED_FLT));
  needed.push_back(predicated_real(b, PRED_FLT));
  needed.push_back(predicated_real(a, b, PRED_LIN));
  return new sub_flt_lin_scheme(real, needed);
}

// FLT(a - b) <- FLT(a), FLT(b), LIN(b, a)
REGISTER_SCHEME_BEGIN(sub_flt_lin_rev);
sub_flt_lin_rev_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "sub_flt_lin_rev") {}
REGISTER_SCHEME_END_PATTERN(sub_flt_lin_rev, predicated_real(pattern(0) - pattern(1), PRED_FLT));

void sub_flt_lin_rev_scheme::compute(property const hyps[], property &res, std::string &) const {
  static interval r(0.5, 2);
  if (hyps[2].bnd() <= r)
    res.cst() = std::max(hyps[0].cst(), hyps[1].cst());
  else res.clear();
}

proof_scheme *sub_flt_lin_rev_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];

  preal_vect needed;
  needed.push_back(predicated_real(a, PRED_FLT));
  needed.push_back(predicated_real(b, PRED_FLT));
  needed.push_back(predicated_real(b, a, PRED_LIN));
  return new sub_flt_lin_rev_scheme(real, needed);
}

// FLT(a + b) <- FLT(a), FLT(b), LIN(a, b)
REGISTER_SCHEME_BEGIN(add_flt_lin);
add_flt_lin_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "add_flt_lin") {}
REGISTER_SCHEME_END_PATTERN(add_flt_lin, predicated_real(pattern(0) + pattern(1), PRED_FLT));

void add_flt_lin_scheme::compute(property const hyps[], property &res, std::string &) const {
  static interval r(-2, -0.5);
  if (hyps[2].bnd() <= r)
    res.cst() = std::max(hyps[0].cst(), hyps[1].cst());
  else res.clear();
}

proof_scheme *add_flt_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];

  preal_vect needed;
  needed.push_back(predicated_real(a, PRED_FLT));
  needed.push_back(predicated_real(b, PRED_FLT));
  needed.push_back(predicated_real(a, b, PRED_LIN));
  return new add_flt_lin_scheme(real, needed);
}

// FLT(a + b) <- FLT(a), FLT(b), LIN(b, a)
REGISTER_SCHEME_BEGIN(add_flt_lin_rev);
add_flt_lin_rev_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "add_flt_lin_rev") {}
REGISTER_SCHEME_END_PATTERN(add_flt_lin_rev, predicated_real(pattern(0) + pattern(1), PRED_FLT));

void add_flt_lin_rev_scheme::compute(property const hyps[], property &res, std::string &) const {
  static interval r(-2, -0.5);
  if (hyps[2].bnd() <= r)
    res.cst() = std::max(hyps[0].cst(), hyps[1].cst());
  else res.clear();
}

proof_scheme *add_flt_lin_rev_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];

  preal_vect needed;
  needed.push_back(predicated_real(a, PRED_FLT));
  needed.push_back(predicated_real(b, PRED_FLT));
  needed.push_back(predicated_real(b, a, PRED_LIN));
  return new add_flt_lin_rev_scheme(real, needed);
}

// LIN(a, b) <- NZR(b), BND(a / b)
REGISTER_SCHEME_BEGIN(lin_of_bnd_div);
lin_of_bnd_div_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "lin_of_bnd_div", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(lin_of_bnd_div, predicated_real(pattern(0), pattern(1), PRED_LIN));

void lin_of_bnd_div_scheme::compute(property const hyps[], property &res, std::string &) const {
  res.bnd() = hyps[1].bnd();
}

proof_scheme *lin_of_bnd_div_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  preal_vect needed;
  needed.push_back(predicated_real(b, PRED_NZR));
  needed.push_back(predicated_real(normalize(real_op(a, BOP_DIV, b)), PRED_BND));
  return new lin_of_bnd_div_scheme(real, needed);
}

// BND(a / b) <- NZR(b), LIN(a, b)
REGISTER_SCHEME_BEGIN(bnd_div_of_lin);
bnd_div_of_lin_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "bnd_div_of_lin", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(bnd_div_of_lin, predicated_real(pattern(0) / pattern(1), PRED_BND));

void bnd_div_of_lin_scheme::compute(property const hyps[], property &res, std::string &) const {
  res.bnd() = hyps[1].bnd();
}

proof_scheme *bnd_div_of_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];

  if (a == b)
    return NULL;

  preal_vect needed;
  needed.push_back(predicated_real(b, PRED_NZR));
  needed.push_back(predicated_real(a, b, PRED_LIN));
  return new bnd_div_of_lin_scheme(real, needed);
}

// BND(a / b) <- LIN(a, b)
REGISTER_SCHEME_BEGIN(bnd_of_lin);
  bnd_of_lin_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "bnd_of_lin", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(bnd_of_lin, predicated_real(pattern(0) / pattern(1), PRED_BND));

void bnd_of_lin_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  if (!contains_zero(i)) return;
  res.bnd() = i;
}

proof_scheme *bnd_of_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[0]->is_constant && holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], holders[1], PRED_LIN));
  return new bnd_of_lin_scheme(real, hyps);
}

#if 0
// LIN(a, c) <- LIN(a, b), LIN(b, c)
REGISTER_SCHEME_BEGIN(compose_lin);
compose_lin_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "compose_lin_xx") {}
REGISTER_SCHEME_END_PATTERN(compose_lin, predicated_real(pattern(0), pattern(2), PRED_LIN));

void compose_lin_scheme::compute(property const hyps[], property &res, std::string &name) const {
  res.bnd() = hyps[0].bnd() * hyps[1].bnd();
  name[name.size() - 2] = 'o' + sign(hyps[0].bnd());
  name[name.size() - 1] = 'o' + sign(hyps[1].bnd());
}

proof_scheme *compose_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];

  preal_vect needed;
  needed.push_back(predicated_real(a, b, PRED_LIN));
  needed.push_back(predicated_real(b, c, PRED_LIN));
  return new compose_lin_scheme(real, needed);
}
#endif

// LIN(b, c) <- LIN(a, c), REL(b, a)
REGISTER_SCHEME_BEGIN(lin_of_lin_rel1);
lin_of_lin_rel1_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "lin_of_lin_rel1_x") {}
REGISTER_SCHEME_END_PATTERN(lin_of_lin_rel1, predicated_real(pattern(1), pattern(2), PRED_LIN));

void lin_of_lin_rel1_scheme::compute(property const hyps[], property &res, std::string &name) const {
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  if (lower(i2) < -1) return;
  res.bnd() = i1 * (interval(1, 1) + i2);
  name[name.size() - 1] = 'o' + sign(i1);
}

proof_scheme *lin_of_lin_rel1_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  preal_vect needed;
  needed.push_back(predicated_real(holders[0], holders[2], PRED_LIN));
  needed.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new lin_of_lin_rel1_scheme(real, needed);
}

// LIN(c, b) <- LIN(c, a), REL(b, a)
REGISTER_SCHEME_BEGIN(lin_of_lin_rel2);
lin_of_lin_rel2_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "lin_of_lin_rel2_x") {}
REGISTER_SCHEME_END_PATTERN(lin_of_lin_rel2, predicated_real(pattern(2), pattern(1), PRED_LIN));

void lin_of_lin_rel2_scheme::compute(property const hyps[], property &res, std::string &name) const {
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  if (lower(i2) <= -1) return;
  res.bnd() = i1 / (interval(1, 1) + i2);
  name[name.size() - 1] = 'o' + sign(i1);
}

proof_scheme *lin_of_lin_rel2_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  preal_vect needed;
  needed.push_back(predicated_real(holders[2], holders[0], PRED_LIN));
  needed.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new lin_of_lin_rel2_scheme(real, needed);
}

// LIN(a, c) <- LIN(b, c), REL(b, a)
REGISTER_SCHEME_BEGIN(lin_of_lin_rel3);
lin_of_lin_rel3_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "lin_of_lin_rel3_x") {}
REGISTER_SCHEME_END_PATTERN(lin_of_lin_rel3, predicated_real(pattern(0), pattern(2), PRED_LIN));

void lin_of_lin_rel3_scheme::compute(property const hyps[], property &res, std::string &name) const {
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  if (lower(i2) <= -1) return;
  res.bnd() = i1 / (interval(1, 1) + i2);
  name[name.size() - 1] = 'o' + sign(i1);
}

proof_scheme *lin_of_lin_rel3_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  preal_vect needed;
  needed.push_back(predicated_real(holders[1], holders[2], PRED_LIN));
  needed.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new lin_of_lin_rel3_scheme(real, needed);
}

#if 0
// LIN(b - a) <- LIN(a - b)
REGISTER_SCHEME_BEGIN(lin_of_opp_lin);
lin_of_opp_lin_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "lin_of_opp_lin") {}
REGISTER_SCHEME_END_PATTERN(lin_of_opp_lin, predicated_real(pattern(0) - pattern(1), pattern(2), PRED_LIN));

void lin_of_opp_lin_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = -hyps[0].bnd();
}

proof_scheme *lin_of_opp_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];

  if (a == c || b == c) return NULL;

  preal_vect hyps;
  hyps.push_back(predicated_real(normalize(real_op(b, BOP_SUB, a)), c, PRED_LIN));
  return new lin_of_opp_lin_scheme(real, hyps);
}
#endif

// LIN(a * b, c * d) <- LIN(a, c), LIN(b, d)
REGISTER_SCHEME_BEGIN(mul_lin);
mul_lin_scheme(predicated_real const &r, preal_vect const &v)
  : proof_scheme(r, v, "mul_ll_xx") {}
REGISTER_SCHEME_END_PATTERN(mul_lin, predicated_real(pattern(0) * pattern(1), pattern(2) * pattern(3), PRED_LIN));

void mul_lin_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  res.bnd() = hyps[0].bnd() * hyps[1].bnd();
  name[name.size() - 2] = 'o' + sign(hyps[0].bnd());
  name[name.size() - 1] = 'o' + sign(hyps[1].bnd());
}

proof_scheme *mul_lin_scheme::factory(predicated_real const &real, ast_real_vect const &holders) {
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  ast_real const *d = holders[3];

  // mul_fi?m are better theorems when the expression can be simplified
  if (a == c || a == d || b == c || b == d) return NULL;

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_LIN));
  needed.push_back(predicated_real(b, d, PRED_LIN));
  return new mul_lin_scheme(real, needed);
}

// LIN(a, a)
REGISTER_SCHEME_BEGIN(lin_refl);
lin_refl_scheme(predicated_real const &r) : proof_scheme(r, preal_vect(), "lin_refl", UPD_TRIV) {}
REGISTER_SCHEME_END_PATTERN(lin_refl, predicated_real(pattern(0), pattern(0), PRED_LIN));

void lin_refl_scheme::compute(property const[], property &res, std::string &) const {
  res.bnd() = interval(1, 1);
}

proof_scheme *lin_refl_scheme::factory(predicated_real const &real, ast_real_vect const &) {
  return new lin_refl_scheme(real);
}

// LIN(b - a, c) <- LIN(a, c), REL(b, a)
REGISTER_SCHEME_BEGIN(lin_of_lin_rel);
lin_of_lin_rel_scheme(predicated_real const &r, preal_vect const v) : proof_scheme(r, v, "lin_of_lin_rel_xx") {}
REGISTER_SCHEME_END_PATTERN(lin_of_lin_rel, predicated_real(pattern(1) - pattern(0), pattern(2), PRED_LIN));

void lin_of_lin_rel_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  res.bnd() = hyps[0].bnd() * hyps[1].bnd();
  name[name.size() - 2] = 'o' + sign(hyps[0].bnd());
  name[name.size() - 1] = 'o' + sign(hyps[1].bnd());
}

proof_scheme *lin_of_lin_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  if (a == c || !is_approx(b, a)) return NULL;

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_LIN));
  needed.push_back(predicated_real(b, a, PRED_REL));
  return new lin_of_lin_rel_scheme(real, needed);
}

// BND(a - b) <- EQL(a, b)
REGISTER_SCHEME_BEGIN(sub_of_eql);
  sub_of_eql_scheme(predicated_real const &r, predicated_real const &n)
    : proof_scheme(r, preal_vect(1, n), "sub_of_eql", UPD_TRIV) {}
REGISTER_SCHEME_END_PATTERN(sub_of_eql, predicated_real(pattern(0) - pattern(1), PRED_BND));

void sub_of_eql_scheme::compute(property const [], property &res, std::string &) const
{
  res.bnd() = zero();
}

proof_scheme *sub_of_eql_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1] || is_constant(real.real())) return NULL;
  return new sub_of_eql_scheme(real, predicated_real(holders[0], holders[1], PRED_EQL));
}

// EQL(a, b) <- BND(a), BND(b)
REGISTER_SCHEME_BEGIN(eql_of_cst);
  eql_of_cst_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "eql_of_cst") {}
REGISTER_SCHEME_END_PREDICATE(eql_of_cst);

void eql_of_cst_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (!is_singleton(hyps[1].bnd()) || !(hyps[0].bnd() <= hyps[1].bnd())) res = property();
}

proof_scheme *eql_of_cst_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_EQL ||
      !is_constant(real.real()) || !is_constant(real.real2())) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(real.real(), PRED_BND));
  hyps.push_back(predicated_real(real.real2(), PRED_BND));
  return new eql_of_cst_scheme(real, hyps);
}

// REL(a, a)
REGISTER_SCHEME_BEGIN(rel_refl);
  rel_refl_scheme(predicated_real const &r)
    : proof_scheme(r, preal_vect(), "rel_refl", UPD_TRIV) {}
REGISTER_SCHEME_END_PATTERN(rel_refl, predicated_real(pattern(0), pattern(0), PRED_REL));

void rel_refl_scheme::compute(property const [], property &res, std::string &) const
{
  res.bnd() = zero();
}

proof_scheme *rel_refl_scheme::factory(predicated_real const &real, ast_real_vect const &)
{
  return new rel_refl_scheme(real);
}

// EQL(b, c) <- EQL(b, a), EQL(a, c)
// EQL(c, a) <- EQL(c, b), EQL(b, a)
REGISTER_SCHEME_BEGIN(eql_trans);
  eql_trans_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "eql_trans") {}
 public:
  static proof_scheme *factory2(predicated_real const &, ast_real_vect const &);
REGISTER_SCHEME_END_PATTERN(eql_trans, predicated_real(pattern(1), pattern(2), PRED_EQL));

void eql_trans_scheme::compute(property const [], property &, std::string &) const
{
}

proof_scheme *eql_trans_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[2]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_EQL));
  hyps.push_back(predicated_real(holders[0], holders[2], PRED_EQL));
  return new eql_trans_scheme(real, hyps);
}

proof_scheme *eql_trans_scheme::factory2(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[1] == holders[2]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[2], holders[1], PRED_EQL));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_EQL));
  return new eql_trans_scheme(real, hyps);
}

static factory_creator eql_trans_scheme_register2(&eql_trans_scheme::factory2,
  predicated_real(pattern(2), pattern(0), PRED_EQL));

// COMPUTATION
REGISTER_SCHEME_BEGIN(computation);
  real_op const *naked_real;
  computation_scheme(ast_real const *r1, real_op const *r2, preal_vect const &v, char const *n)
    : proof_scheme(predicated_real(r1, PRED_BND), v, n), naked_real(r2) {}
REGISTER_SCHEME_END(computation);

void computation_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  real_op const *r = naked_real;
  switch (r->ops.size()) {
  case 1: {
    interval const &i = hyps[0].bnd();
    switch (r->type) {
    case UOP_NEG:
      res.bnd() = -i;
      return;
    case UOP_SQRT:
      if (lower(i) < 0) return;
      res.bnd() = sqrt(i);
      return;
#if 0
    case UOP_ABS:
      return create_theorem(1, &res, property(real, abs(i)), std::string("abs_") += ('o' + sign(i)), &abs_updater);
#endif
    default:
      assert(false);
    }
    break; }
  case 2: {
    bool same_ops = r->ops[0] == r->ops[1];
    if (same_ops) {
      if (r->type == BOP_SUB) { res.bnd() = zero(); return; }
      if (r->type == BOP_DIV) {
        number one(1);
        res.bnd() = interval(one, one);
        return;
      }
    }
    interval const &i1 = hyps[0].bnd();
    if (same_ops && r->type == BOP_MUL) {
      res.bnd() = square(i1);
      return;
    }
    interval const &i2 = hyps[1].bnd();
    switch (r->type) {
    case BOP_ADD: res.bnd() = i1 + i2; return;
    case BOP_SUB: res.bnd() = i1 - i2; return;
    case BOP_MUL:
      res.bnd() = i1 * i2;
      name = "mul_xx";
      name[name.size() - 2] = 'o' + sign(i1);
      name[name.size() - 1] = 'o' + sign(i2);
      return;
    case BOP_DIV:
      if (contains_zero(i2)) return;
      res.bnd() = i1 / i2;
      name = "div_xx";
      name[name.size() - 2] = 'o' + sign(i1);
      name[name.size() - 1] = 'o' + sign(i2);
      return;
    default:
      assert(false);
    } }
  default:
    assert(false);
  }
}

proof_scheme *computation_scheme::factory(ast_real const *real)
{
  ast_real const *r = real;
  if (hidden_real const *h = boost::get< hidden_real const >(real))
    r = h->real;
  real_op const *p = boost::get< real_op const >(r);
  if (!p || p->fun || p->type == UOP_ABS) return NULL;
  ast_real_vect const &ops = p->ops;
  preal_vect needed;
  char const *name = "";
  if (ops.size() != 2 || ops[0] != ops[1]) {
    generic:
    needed.reserve(ops.size());
    for (ast_real const *i: p->ops)
      needed.push_back(predicated_real(i, PRED_BND));
    switch (p->type) {
    case UOP_NEG: name = "neg"; break;
    case UOP_ABS: name = "abs"; break;
    case UOP_SQRT: name = "sqrt"; break;
    case BOP_ADD: name = "add"; break;
    case BOP_SUB: name = "sub"; break;
    default: break;
    }
  } else {
    switch (p->type) {
    case BOP_SUB: name = "sub_refl"; break;
    case BOP_DIV: name = "div_refl"; needed.push_back(predicated_real(ops[0], PRED_NZR)); break;
    case BOP_MUL: name = "square"; needed.push_back(predicated_real(ops[0], PRED_ABS)); break;
    default: goto generic;
    }
  }
  return new computation_scheme(real, p, needed, name);
}

// COMPUTATION_ABS
REGISTER_SCHEME_BEGIN(computation_abs);
  computation_abs_scheme(predicated_real const &r, preal_vect const &v, char const *n, update u)
    : proof_scheme(r, v, n, u) {}
REGISTER_SCHEME_END_PREDICATE(computation_abs);

void computation_abs_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  real_op const *r = boost::get< real_op const >(real.real());
  assert(r);
  interval &i = res.bnd();
  switch (r->ops.size()) {
  case 1:
    i = hyps[0].bnd();
    return;
  case 2: {
    interval const &i1 = hyps[0].bnd();
    interval const &i2 = hyps[1].bnd();
    switch (r->type) {
    case BOP_ADD:
    case BOP_SUB:
      i = interval(lower(abs(i1 - i2)), upper(i1 + i2));
      name = r->type == BOP_ADD ? "add_aa_x" : "sub_aa_x";
      name[name.size() - 1] = lower(i) > 0 ? (lower(i1) > upper(i2) ? 'p' : 'n') : 'o';
      return;
    case BOP_MUL:
      i = i1 * i2;
      return;
    case BOP_DIV:
      if (contains_zero(i2)) return;
      i = i1 / i2;
      return;
    default:
      assert(false);
    } }
  default:
    assert(false);
  }
}

proof_scheme *computation_abs_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_ABS) return NULL;
  real_op const *p = boost::get< real_op const >(real.real());
  if (!p || p->fun || p->type == UOP_SQRT) return NULL;
  if (is_constant(real.real())) return NULL;
  preal_vect needed;
  needed.reserve(p->ops.size());
  for (ast_real const *i: p->ops)
    needed.push_back(predicated_real(i, PRED_ABS));
  char const *name = "";
  update upd = UPD_SEEK;
  switch (p->type) {
  case UOP_NEG: name = "neg_a"; upd = UPD_COPY; break;
  case UOP_ABS: name = "abs_a"; upd = UPD_COPY; break;
  case BOP_MUL: name = "mul_aa"; break;
  case BOP_DIV: name = "div_aa"; break;
  default: break;
  }
  return new computation_abs_scheme(real, needed, name, upd);
}

// COMPUTATION_LIN
REGISTER_SCHEME_BEGIN(computation_lin);
  real_op const *naked_real;
  computation_lin_scheme(predicated_real const &r, real_op const *nr, preal_vect const &v, char const *n)
    : proof_scheme(r, v, n), naked_real(nr) {}
REGISTER_SCHEME_END_PREDICATE(computation_lin);

void computation_lin_scheme::compute(property const hyps[], property &res, std::string &) const
{
  real_op const *r = naked_real;
  switch (r->ops.size()) {
  case 1: {
    interval const &i = hyps[0].bnd();
    switch (r->type) {
    case UOP_NEG:
      res.bnd() = -i;
      return;
    default:
      assert(false);
    }
    break; }
  case 2: {
    interval const &i1 = hyps[0].bnd();
    interval const &i2 = hyps[1].bnd();
    switch (r->type) {
    case BOP_ADD: res.bnd() = i1 + i2; return;
    case BOP_SUB: res.bnd() = i1 - i2; return;
    default:
      assert(false);
    } }
  default:
    assert(false);
  }
}

proof_scheme *computation_lin_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_LIN) return NULL;
  ast_real const *r = real.real();
  if (hidden_real const *h = boost::get<hidden_real const>(r))
    r = h->real;
  real_op const *p = boost::get<real_op const>(r);
  if (!p || p->fun) return NULL;
  ast_real_vect const &ops = p->ops;
  preal_vect needed;
  char const *name = "";
  needed.reserve(ops.size());
  for (ast_real const *i: p->ops)
    needed.push_back(predicated_real(i, real.real2(), PRED_LIN));
  switch (p->type) {
  case UOP_NEG: name = "minus_lin"; break;
  case BOP_ADD: name = "add_ll"; break;
  case BOP_SUB:
    if (p->ops[1] == real.real2()) return NULL;
    name = "sub_ll";
    break;
  default: return NULL;
  }
  return new computation_lin_scheme(real, p, needed, name);
}

// ABS(a) <- BND(a * a)
REGISTER_SCHEME_BEGIN(square_rev);
  square_rev_scheme(predicated_real const &r, ast_real const *u)
    : proof_scheme(r, preal_vect(1, predicated_real(u, PRED_ABS)), "square_rev") {}
REGISTER_SCHEME_END_PATTERN_USER(square_rev, predicated_real(pattern(0), PRED_ABS), pattern(0) * pattern(0));

void square_rev_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = sqrt(hyps[0].bnd());
}

proof_scheme *square_rev_scheme::factory(predicated_real const &real, ast_real_vect const &v)
{
  ast_real const *u = normalize(ast_real(real_op(v[0], BOP_MUL, v[0])));
  return new square_rev_scheme(real, u);
}

// BND(a) <- ABS(a)
REGISTER_SCHEME_BEGIN(bnd_of_abs);
  bnd_of_abs_scheme(ast_real const *r)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, predicated_real(r, PRED_ABS)), "bnd_of_abs") {}
REGISTER_SCHEME_END(bnd_of_abs);

void bnd_of_abs_scheme::compute(property const hyps[], property &res, std::string &) const
{
  number const &num = upper(hyps[0].bnd());
  res.bnd() = interval(-num, num);
}

proof_scheme *bnd_of_abs_scheme::factory(ast_real const *real)
{
  if (is_hidden(real) || is_constant(real)) return NULL;
  real_op const *p = boost::get< real_op const >(real);
  if (p && p->type == UOP_ABS) return NULL;
  return new bnd_of_abs_scheme(real);
}

// ABS(a) <- BND(a)
REGISTER_SCHEME_BEGIN(abs_of_bnd);
  abs_of_bnd_scheme(predicated_real const &r)
    : proof_scheme(r, one_needed(r.real()), "abs_of_bnd_x") {}
REGISTER_SCHEME_END_PREDICATE(abs_of_bnd);

void abs_of_bnd_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &i = hyps[0].bnd();
  res.bnd() = abs(i);
  name[name.size() - 1] = 'o' + sign(i);
}

proof_scheme *abs_of_bnd_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_ABS) return NULL;
  real_op const *p = boost::get< real_op const >(real.real());
  if (p && p->type == UOP_ABS) return NULL;
  return new abs_of_bnd_scheme(real);
}

// BND(a) <- BND(a), ABS(a)
REGISTER_SCHEME_BEGIN(bnd_of_bnd_abs);
  bnd_of_bnd_abs_scheme(preal_vect const &v) : proof_scheme(v[0], v, "") {}
REGISTER_SCHEME_END(bnd_of_bnd_abs);

void bnd_of_bnd_abs_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &ib = hyps[0].bnd(), &ia = hyps[1].bnd();
  number const &iba = lower(ib), &ibb = upper(ib), &iab = lower(ia), &iaa = -iab;
  bool b1 = iba <= iaa, b2 = iab <= ibb;
  if (b1 == b2) return;
  res.bnd() = b1 ? interval(iba, iaa) : interval(iab, ibb);
  name = b1 ? "bnd_of_bnd_abs_n" : "bnd_of_bnd_abs_p";
}

proof_scheme *bnd_of_bnd_abs_scheme::factory(ast_real const *real)
{
  if (is_hidden(real) || is_constant(real)) return NULL;
  real_op const *p = boost::get< real_op const >(real);
  if (p && p->type == UOP_ABS) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(real, PRED_BND));
  hyps.push_back(predicated_real(real, PRED_ABS));
  return new bnd_of_bnd_abs_scheme(hyps);
}

// BND(|a|) <- ABS(a)
REGISTER_SCHEME_BEGIN(uabs_of_abs);
  uabs_of_abs_scheme(ast_real const *r, predicated_real const &v)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(1, v), "uabs_of_abs", UPD_COPY) {}
REGISTER_SCHEME_END(uabs_of_abs);

void uabs_of_abs_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = hyps[0].bnd();
}

proof_scheme *uabs_of_abs_scheme::factory(ast_real const *real)
{
  real_op const *p = boost::get< real_op const >(real);
  if (!p || p->type != UOP_ABS) return NULL;
  return new uabs_of_abs_scheme(real, predicated_real(p->ops[0], PRED_ABS));
}

// ABS(a) <- BND(|a|)
REGISTER_SCHEME_BEGIN(abs_of_uabs);
  abs_of_uabs_scheme(predicated_real const &r, predicated_real const &v)
    : proof_scheme(r, preal_vect(1, v), "abs_of_uabs", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN_USER(abs_of_uabs, predicated_real(pattern(0), PRED_ABS), pattern::abs(pattern(0)));

void abs_of_uabs_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (lower(hyps[0].bnd()) < 0) return;
  res.bnd() = hyps[0].bnd();
}

proof_scheme *abs_of_uabs_scheme::factory(predicated_real const &real, ast_real_vect const &)
{
  ast_real const *r = real.real();
  if (is_constant(r)) return NULL;
  ast_real const *ra = normalize(ast_real(real_op(UOP_ABS, r)));
  return new abs_of_uabs_scheme(real, predicated_real(ra, PRED_BND));
}

// BND(literal)
REGISTER_SCHEME_BEGIN(number);
  number_scheme(ast_real const *r, char const *n)
    : proof_scheme(predicated_real(r, PRED_BND), preal_vect(), n, UPD_TRIV) {}
REGISTER_SCHEME_END(number);

interval create_interval(ast_number const *, ast_number const *, bool widen = true);

void number_scheme::compute(property const[], property &res, std::string &) const
{
  ast_number const *const *r = boost::get< ast_number const *const >(real.real());
  assert(r);
  res.bnd() = create_interval(*r, *r);
}

proof_scheme *number_scheme::factory(ast_real const *real)
{
  ast_number const *const *r = boost::get< ast_number const *const >(real);
  if (!r) return NULL;
  char const *s;
  if ((**r).base == 0 || (**r).exponent == 0) s = "constant1";
  else if ((**r).base == 2) s = "constant2";
  else s = "constant10";
  return new number_scheme(real, s);
}

// FIX(-a) <- FIX(a)
// FIX(|a|) <- FIX(a)
// FLT(-a) <- FLT(a)
// FLT(|a|) <- FLT(a)
REGISTER_SCHEME_BEGIN(neg_abs_fix_flt);
  neg_abs_fix_flt_scheme(predicated_real const &r, predicated_real const &v, char const *n)
    : proof_scheme(r, preal_vect(1, v), n) {}
REGISTER_SCHEME_END_PREDICATE(neg_abs_fix_flt);

void neg_abs_fix_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.cst() = hyps[0].cst();
}

proof_scheme *neg_abs_fix_flt_scheme::factory(predicated_real const &real)
{
  predicate_type t = real.pred();
  if (t != PRED_FIX && t != PRED_FLT) return NULL;
  real_op const *p = boost::get< real_op const >(real.real());
  if (!p || (p->type != UOP_ABS && p->type != UOP_NEG)) return NULL;
  std::string name = p->type == UOP_ABS ? "abs_" : "neg_";
  if (is_constant(p->ops[0])) return NULL;
  return new neg_abs_fix_flt_scheme(real, predicated_real(p->ops[0], t), (name + (t == PRED_FIX ? "fix" : "flt")).c_str());
}

// FIX(a + b) <- FIX(a), FIX(b)
// FIX(a - b) <- FIX(a), FIX(b)
REGISTER_SCHEME_BEGIN(add_sub_fix);
  add_sub_fix_scheme(predicated_real const &r, preal_vect const &v, char const *n)
    : proof_scheme(r, v, n) {}
REGISTER_SCHEME_END_PREDICATE(add_sub_fix);

void add_sub_fix_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.cst() = std::min(hyps[0].cst(), hyps[1].cst());
}

proof_scheme *add_sub_fix_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_FIX) return NULL;
  real_op const *p = boost::get< real_op const >(real.real());
  if (!p || (p->type != BOP_ADD && p->type != BOP_SUB)) return NULL;
  if (is_constant(p->ops[0]) && is_constant(p->ops[1])) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(p->ops[0], PRED_FIX));
  hyps.push_back(predicated_real(p->ops[1], PRED_FIX));
  return new add_sub_fix_scheme(real, hyps, p->type == BOP_SUB ? "sub_fix" : "add_fix");
}

// FIX(a * b) <- FIX(a), FIX(b)
// FLT(a * b) <- FLT(a), FLT(b)
REGISTER_SCHEME_BEGIN(mul_fix_flt);
  mul_fix_flt_scheme(predicated_real const &r, preal_vect const &v, char const *n)
    : proof_scheme(r, v, n) {}
REGISTER_SCHEME_END_PREDICATE(mul_fix_flt);

void mul_fix_flt_scheme::compute(property const hyps[], property &res, std::string &) const
{
  long c0 = hyps[0].cst(), c1 = hyps[1].cst();
  if (res.real.pred() == PRED_FLT) {
    if (c0 == 1) c0 = 0;
    else if (c1 == 1) c1 = 0;
  }
  res.cst() = c0 + c1;
}

proof_scheme *mul_fix_flt_scheme::factory(predicated_real const &real)
{
  predicate_type t = real.pred();
  if (t != PRED_FIX && t != PRED_FLT) return NULL;
  real_op const *p = boost::get< real_op const >(real.real());
  if (!p || p->type != BOP_MUL) return NULL;
  if (is_constant(p->ops[0]) && is_constant(p->ops[1])) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(p->ops[0], t));
  hyps.push_back(predicated_real(p->ops[1], t));
  return new mul_fix_flt_scheme(real, hyps, t == PRED_FIX ? "mul_fix" : "mul_flt");
}

// FIX(a) <- FLT(a), ABS(a)
REGISTER_SCHEME_BEGIN(fix_of_flt_bnd);
  fix_of_flt_bnd_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "fix_of_flt_bnd") {}
REGISTER_SCHEME_END_PREDICATE(fix_of_flt_bnd);

void fix_of_flt_bnd_scheme::compute(property const hyps[], property &res, std::string &) const
{
  mpz_t m;
  mpz_init(m);
  int e, s;
  split_exact(lower(hyps[1].bnd()).data->val, m, e, s);
  if (s <= 0) { mpz_clear(m); res.clear(); return; }
  e += mpz_sizeinbase(m, 2);
  mpz_clear(m);
  res.cst() = e - hyps[0].cst();
}

proof_scheme *fix_of_flt_bnd_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_FIX) return NULL;
  ast_real const *r = real.real();
  if (is_changing_sign(r) || is_constant(r)) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(r, PRED_FLT));
  hyps.push_back(predicated_real(r, PRED_ABS));
  return new fix_of_flt_bnd_scheme(real, hyps);
}

// FLT(a) <- FIX(a), ABS(a)
REGISTER_SCHEME_BEGIN(flt_of_fix_bnd);
  flt_of_fix_bnd_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "flt_of_fix_bnd") {}
REGISTER_SCHEME_END_PREDICATE(flt_of_fix_bnd);

void flt_of_fix_bnd_scheme::compute(property const hyps[], property &res, std::string &) const
{
  mpz_t m;
  mpz_init(m);
  int e, s;
  split_exact(upper(hyps[1].bnd()).data->val, m, e, s);
  if (s <= 0) { mpz_clear(m); res.clear(); return; }
  e += mpz_sizeinbase(m, 2);
  mpz_clear(m);
  res.cst() = e - hyps[0].cst();
}

proof_scheme *flt_of_fix_bnd_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_FLT) return NULL;
  ast_real const *r = real.real();
  if (is_changing_sign(r) || is_constant(r)) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(r, PRED_FIX));
  hyps.push_back(predicated_real(r, PRED_ABS));
  return new flt_of_fix_bnd_scheme(real, hyps);
}

// FIX(a) <- ABS(a)
REGISTER_SCHEME_BEGIN(fix_of_singleton_bnd);
  fix_of_singleton_bnd_scheme(predicated_real const &r, predicated_real const &v)
    : proof_scheme(r, preal_vect(1, v), "fix_of_singleton_bnd") {}
REGISTER_SCHEME_END_PREDICATE(fix_of_singleton_bnd);

void fix_of_singleton_bnd_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  number const &l = lower(i);
  if (upper(i) != l) { res.clear(); return; }
  mpz_t m;
  mpz_init(m);
  int e, s;
  split_exact(l.data->val, m, e, s);
  mpz_clear(m);
  if (s == 0) { res.clear(); return; }
  res.cst() = e;
}

proof_scheme *fix_of_singleton_bnd_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_FIX) return NULL;
  return new fix_of_singleton_bnd_scheme(real, predicated_real(real.real(), PRED_ABS));
}

// FLT(a) <- ABS(a)
REGISTER_SCHEME_BEGIN(flt_of_singleton_bnd);
  flt_of_singleton_bnd_scheme(predicated_real const &r, predicated_real const &v)
    : proof_scheme(r, preal_vect(1, v), "flt_of_singleton_bnd") {}
REGISTER_SCHEME_END_PREDICATE(flt_of_singleton_bnd);

void flt_of_singleton_bnd_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  number const &l = lower(i);
  if (upper(i) != l) { res.clear(); return; }
  mpz_t m;
  mpz_init(m);
  int e, s;
  split_exact(l.data->val, m, e, s);
  if (s == 0) e = 0;
  else e = mpz_sizeinbase(m, 2);
  mpz_clear(m);
  res.cst() = e;
}

proof_scheme *flt_of_singleton_bnd_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_FLT) return NULL;
  return new flt_of_singleton_bnd_scheme(real, predicated_real(real.real(), PRED_ABS));
}

// BND((b - a) / a) <- NZR(a), REL(b, a)
REGISTER_SCHEME_BEGIN(bnd_of_nzr_rel);
  bnd_of_nzr_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "bnd_of_nzr_rel", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(bnd_of_nzr_rel,
  predicated_real((pattern(1) - pattern(0)) / pattern(0), PRED_BND));

void bnd_of_nzr_rel_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = hyps[1].bnd();
}

proof_scheme *bnd_of_nzr_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[0]->is_constant && holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_NZR));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new bnd_of_nzr_rel_scheme(real, hyps);
}

// BND((b - a) / a) <- REL(b, a)
REGISTER_SCHEME_BEGIN(bnd_of_rel);
  bnd_of_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "bnd_of_rel", UPD_COPY) {}
REGISTER_SCHEME_END_PATTERN(bnd_of_rel,
  predicated_real((pattern(1) - pattern(0)) / pattern(0), PRED_BND));

void bnd_of_rel_scheme::compute(property const hyps[], property &res, std::string &) const
{
  interval const &i = hyps[0].bnd();
  if (!contains_zero(i)) return;
  res.bnd() = i;
}

proof_scheme *bnd_of_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[0]->is_constant && holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new bnd_of_rel_scheme(real, hyps);
}

// REL(a, b) <- LIN(a - b, b)
REGISTER_SCHEME_BEGIN(rel_of_lin);
  rel_of_lin_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "rel_of_lin", UPD_COPY) {}
REGISTER_SCHEME_END_PREDICATE(rel_of_lin);

void rel_of_lin_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (lower(hyps[0].bnd()) <= -1) return;
  res.bnd() = hyps[0].bnd();
}

proof_scheme *rel_of_lin_scheme::factory(predicated_real const &real)
{
  if (real.pred() != PRED_REL) return NULL;
  ast_real const *r1 = real.real(), *r2 = real.real2();
  if (r1 == r2) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(
    normalize(real_op(r1, BOP_SUB, r2)), r2, PRED_LIN));
  return new rel_of_lin_scheme(real, hyps);
}

// REL(a + b, c + d) <- REL(a, c), REL(b, d), LIN(c, c + d)
REGISTER_SCHEME_BEGIN(add_rr_left);
add_rr_left_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "add_rr_left") {}
REGISTER_SCHEME_END_PATTERN(add_rr_left, predicated_real(pattern(0) + pattern(1), pattern(2) + pattern(3), PRED_REL));

void add_rr_left_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = add_relative(hyps[0].bnd(), hyps[1].bnd(), hyps[2].bnd());
}

proof_scheme *add_rr_left_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  ast_real const *d = holders[3];

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_REL));
  needed.push_back(predicated_real(b, d, PRED_REL));
  needed.push_back(predicated_real(c, real.real2(), PRED_LIN));
  return new add_rr_left_scheme(real, needed);
}

// REL(a + b, c + d) <- REL(a, c), REL(b, d), LIN(d, c + d)
REGISTER_SCHEME_BEGIN(add_rr_right);
add_rr_right_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "add_rr_right") {}
REGISTER_SCHEME_END_PATTERN(add_rr_right, predicated_real(pattern(0) + pattern(1), pattern(2) + pattern(3), PRED_REL));

void add_rr_right_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = add_relative(hyps[1].bnd(), hyps[0].bnd(), hyps[2].bnd());
}

proof_scheme *add_rr_right_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  ast_real const *d = holders[3];

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_REL));
  needed.push_back(predicated_real(b, d, PRED_REL));
  needed.push_back(predicated_real(d, real.real2(), PRED_LIN));
  return new add_rr_right_scheme(real, needed);
}

// REL(a - b, c - d) <- REL(a, c), REL(b, d), LIN(c, c - d)
REGISTER_SCHEME_BEGIN(sub_rr_left);
sub_rr_left_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "sub_rr_left") {}
REGISTER_SCHEME_END_PATTERN(sub_rr_left, predicated_real(pattern(0) - pattern(1), pattern(2) - pattern(3), PRED_REL));

void sub_rr_left_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = add_relative(hyps[0].bnd(), hyps[1].bnd(), hyps[2].bnd());
}

proof_scheme *sub_rr_left_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  ast_real const *d = holders[3];

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_REL));
  needed.push_back(predicated_real(b, d, PRED_REL));
  needed.push_back(predicated_real(c, real.real2(), PRED_LIN));
  return new sub_rr_left_scheme(real, needed);
}

// REL(a - b, c - d) <- REL(a, c), REL(b, d), LIN(d, c - d)
REGISTER_SCHEME_BEGIN(sub_rr_right);
sub_rr_right_scheme(predicated_real const &r, preal_vect const &v) : proof_scheme(r, v, "sub_rr_right") {}
REGISTER_SCHEME_END_PATTERN(sub_rr_right, predicated_real(pattern(0) - pattern(1), pattern(2) - pattern(3), PRED_REL));

void sub_rr_right_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = add_relative(hyps[1].bnd(), hyps[0].bnd(), - hyps[2].bnd());
}

proof_scheme *sub_rr_right_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  ast_real const *a = holders[0];
  ast_real const *b = holders[1];
  ast_real const *c = holders[2];
  ast_real const *d = holders[3];

  preal_vect needed;
  needed.push_back(predicated_real(a, c, PRED_REL));
  needed.push_back(predicated_real(b, d, PRED_REL));
  needed.push_back(predicated_real(d, real.real2(), PRED_LIN));
  return new sub_rr_right_scheme(real, needed);
}

// REL(a * b, c * d) <- REL(a, c), REL(b, d)
REGISTER_SCHEME_BEGIN(computation_rel_mul);
  computation_rel_mul_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "mul_rr") {}
REGISTER_SCHEME_END_PATTERN(computation_rel_mul,
  predicated_real(pattern(0) * pattern(1), pattern(2) * pattern(3), PRED_REL));

void computation_rel_mul_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = compose_relative(hyps[0].bnd(), hyps[1].bnd());
}

proof_scheme *computation_rel_mul_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[2] || holders[1] == holders[3]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], holders[2], PRED_REL));
  hyps.push_back(predicated_real(holders[1], holders[3], PRED_REL));
  return new computation_rel_mul_scheme(real, hyps);
}

// REL(a / b, c / d) <- REL(a, c), REL(b, d)
REGISTER_SCHEME_BEGIN(computation_rel_div);
  computation_rel_div_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "div_rr") {}
REGISTER_SCHEME_END_PATTERN(computation_rel_div,
  predicated_real(pattern(0) / pattern(1), pattern(2) / pattern(3), PRED_REL));

void computation_rel_div_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = compose_relative_inv(hyps[0].bnd(), hyps[1].bnd());
}

proof_scheme *computation_rel_div_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[2] || holders[1] == holders[3]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], holders[2], PRED_REL));
  hyps.push_back(predicated_real(holders[1], holders[3], PRED_REL));
  return new computation_rel_div_scheme(real, hyps);
}

// REL(a / b, a / c) <- REL(b, c)
REGISTER_SCHEME_BEGIN(computation_rel_inv);
  computation_rel_inv_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "inv_r") {}
REGISTER_SCHEME_END_PATTERN(computation_rel_inv,
  predicated_real(pattern(0) / pattern(1), pattern(0) / pattern(2), PRED_REL));

void computation_rel_inv_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = compose_relative_inv(zero(), hyps[0].bnd());
}

proof_scheme *computation_rel_inv_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[1] == holders[2]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], holders[2], PRED_REL));
  return new computation_rel_inv_scheme(real, hyps);
}

// REL(b, c) <- REL(b, a), REL(a, c)
// REL(c, a) <- REL(c, b), REL(b, c)
REGISTER_SCHEME_BEGIN(compose_rel);
  compose_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "compose") {}
 public:
  static proof_scheme *factory2(predicated_real const &, ast_real_vect const &);
REGISTER_SCHEME_END_PATTERN(compose_rel, predicated_real(pattern(1), pattern(2), PRED_REL));

void compose_rel_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = compose_relative(hyps[0].bnd(), hyps[1].bnd());
}

proof_scheme *compose_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[2]) return NULL;
  if (holders[1] == holders[2]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  hyps.push_back(predicated_real(holders[0], holders[2], PRED_REL));
  return new compose_rel_scheme(real, hyps);
}

proof_scheme *compose_rel_scheme::factory2(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[2]) return NULL;
  if (holders[1] == holders[2]) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[2], holders[1], PRED_REL));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new compose_rel_scheme(real, hyps);
}

static factory_creator compose_rel_scheme_register2(&compose_rel_scheme::factory2,
  predicated_real(pattern(2), pattern(0), PRED_REL));

// REL(c * b, d) <- REL(c, d * a'), REL(b, 1/a'), NZR(a')
REGISTER_SCHEME_BEGIN(compose_rel_swap);
  compose_rel_swap_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "compose_swap") {}
REGISTER_SCHEME_END_PATTERN(compose_rel_swap,
  predicated_real(pattern(2) * pattern(1), pattern(3), PRED_REL));

void compose_rel_swap_scheme::compute(property const hyps[], property &res, std::string &) const
{
  res.bnd() = compose_relative(hyps[0].bnd(), hyps[1].bnd());
}

proof_scheme *compose_rel_swap_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  static ast_real const *one = normalize(token_one);
  real_op const *p = boost::get<real_op const>(holders[0]);
  if (!p || p->type != BOP_DIV || p->ops[0] != one) return NULL;
  ast_real const *r = normalize(real_op(holders[3], BOP_MUL, p->ops[1]));
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[2], r, PRED_REL));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  hyps.push_back(predicated_real(p->ops[1], PRED_NZR));
  return new compose_rel_swap_scheme(real, hyps);
}

// BND(b - a) <- REL(b, a), BND(a)
REGISTER_SCHEME_BEGIN(error_of_rel);
  error_of_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "error_of_rel_xx") {}
REGISTER_SCHEME_END_PATTERN(error_of_rel, predicated_real(pattern(1) - pattern(0), PRED_BND));

void error_of_rel_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  res.bnd() = i1 * i2;
  name[name.size() - 2] = 'o' + sign(i1);
  name[name.size() - 1] = 'o' + sign(i2);
}

proof_scheme *error_of_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[0]->is_constant && holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  hyps.push_back(predicated_real(holders[0], PRED_BND));
  return new error_of_rel_scheme(real, hyps);
}

// BND(b) <- REL(b, a), BND(a)
REGISTER_SCHEME_BEGIN(bnd_of_bnd_rel);
  bnd_of_bnd_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "bnd_of_bnd_rel_x") {}
REGISTER_SCHEME_END_PATTERN(bnd_of_bnd_rel, predicated_real(pattern(1), PRED_BND));

void bnd_of_bnd_rel_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  res.bnd() = i1 * (interval(1, 1) + i2);
  name[name.size() - 1] = 'o' + sign(i1);
}

proof_scheme *bnd_of_bnd_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_BND));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new bnd_of_bnd_rel_scheme(real, hyps);
}

// BND(a) <- BND(b), REL(b, a)
REGISTER_SCHEME_BEGIN(bnd_of_rel_bnd);
  bnd_of_rel_bnd_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "bnd_of_rel_bnd_x") {}
REGISTER_SCHEME_END_PATTERN(bnd_of_rel_bnd, predicated_real(pattern(-1), PRED_BND));

void bnd_of_rel_bnd_scheme::compute(property const hyps[], property &res, std::string &name) const
{
  interval const &i1 = hyps[0].bnd(), &i2 = hyps[1].bnd();
  interval i = interval(1, 1) + i2;
  if (contains_zero(i)) return;
  res.bnd() = i1 / i;
  name[name.size() - 1] = 'o' + sign(i1);
}

proof_scheme *bnd_of_rel_bnd_scheme::factory(predicated_real const &real,
  ast_real_vect const &holders)
{
  if (holders[0] == holders[1]) return NULL;
  if (holders[0]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], PRED_BND));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new bnd_of_rel_bnd_scheme(real, hyps);
}

// NZR(-a) <- NZR(a)
REGISTER_SCHEME_BEGIN(neg_nzr);
  neg_nzr_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "neg_nzr") {}
REGISTER_SCHEME_END_PATTERN(neg_nzr, predicated_real(-pattern(0), PRED_NZR));

void neg_nzr_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *neg_nzr_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_NZR));
  return new neg_nzr_scheme(real, hyps);
}

// NZR(a * b) <- NZR(a), NZR(b)
REGISTER_SCHEME_BEGIN(mul_nzr);
  mul_nzr_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "mul_nzr") {}
REGISTER_SCHEME_END_PATTERN(mul_nzr, predicated_real(pattern(0) * pattern(1), PRED_NZR));

void mul_nzr_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *mul_nzr_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_NZR));
  hyps.push_back(predicated_real(holders[1], PRED_NZR));
  return new mul_nzr_scheme(real, hyps);
}

// NZR(a / b) <- NZR(a), NZR(b)
REGISTER_SCHEME_BEGIN(div_nzr);
  div_nzr_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "div_nzr") {}
REGISTER_SCHEME_END_PATTERN(div_nzr, predicated_real(pattern(0) / pattern(1), PRED_NZR));

void div_nzr_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *div_nzr_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_NZR));
  hyps.push_back(predicated_real(holders[1], PRED_NZR));
  return new div_nzr_scheme(real, hyps);
}

// NZR(a) <- NZR(a / b)
REGISTER_SCHEME_BEGIN(div_nzr_rev_l);
  div_nzr_rev_l_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "div_nzr_rev_l") {}
REGISTER_SCHEME_END_PATTERN_USER(div_nzr_rev_l, predicated_real(pattern(0), PRED_NZR), pattern(0) / pattern(1));

void div_nzr_rev_l_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *div_nzr_rev_l_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  preal_vect hyps;
  hyps.push_back(predicated_real(normalize(real_op(holders[0], BOP_DIV, holders[1])), PRED_NZR));
  return new div_nzr_rev_l_scheme(real, hyps);
}

// NZR(b) <- NZR(a / b)
REGISTER_SCHEME_BEGIN(div_nzr_rev_r);
  div_nzr_rev_r_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "div_nzr_rev_r") {}
REGISTER_SCHEME_END_PATTERN_USER(div_nzr_rev_r, predicated_real(pattern(1), PRED_NZR), pattern(0) / pattern(1));

void div_nzr_rev_r_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *div_nzr_rev_r_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  preal_vect hyps;
  hyps.push_back(predicated_real(normalize(real_op(holders[0], BOP_DIV, holders[1])), PRED_NZR));
  return new div_nzr_rev_r_scheme(real, hyps);
}

// NZR(a) <- ABS(a)
REGISTER_SCHEME_BEGIN(nzr_of_abs);
  nzr_of_abs_scheme(predicated_real const &r, predicated_real const &n)
    : proof_scheme(r, preal_vect(1, n), "nzr_of_abs") {}
REGISTER_SCHEME_END_PREDICATE(nzr_of_abs);

void nzr_of_abs_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (contains_zero(hyps[0].bnd())) res = property();
}

proof_scheme *nzr_of_abs_scheme::factory(predicated_real const &real) {
  if (real.pred() != PRED_NZR) return NULL;
  return new nzr_of_abs_scheme(real, predicated_real(real.real(), PRED_ABS));
}

// NZR_OF_UNCONSTRAINED
REGISTER_SCHEME_BEGIN(nzr_of_unconstrained);
  nzr_of_unconstrained_scheme(predicated_real const &r)
    : proof_scheme(r, preal_vect(), "$FALSE") {}
REGISTER_SCHEME_END_PREDICATE(nzr_of_unconstrained);

void nzr_of_unconstrained_scheme::compute(property const[], property &, std::string &) const
{
}

proof_scheme *nzr_of_unconstrained_scheme::factory(predicated_real const &real)
{
  extern bool parameter_constrained;
  if (real.pred() != PRED_NZR || parameter_constrained) return NULL;
  return new nzr_of_unconstrained_scheme(real);
}

// NZR(b) <- NZR(a), REL(b, a)
REGISTER_SCHEME_BEGIN(nzr_of_nzr_rel);
  nzr_of_nzr_rel_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "nzr_of_nzr_rel") {}
REGISTER_SCHEME_END_PATTERN(nzr_of_nzr_rel, predicated_real(pattern(1), PRED_NZR));

void nzr_of_nzr_rel_scheme::compute(property const hyps[], property &res, std::string &) const
{
  if (lower(hyps[1].bnd()) <= -1) res.clear();
}

proof_scheme *nzr_of_nzr_rel_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[1]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[0], PRED_NZR));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new nzr_of_nzr_rel_scheme(real, hyps);
}

// NZR(a) <- NZR(b), REL(b, a)
REGISTER_SCHEME_BEGIN(nzr_of_nzr_rel_rev);
  nzr_of_nzr_rel_rev_scheme(predicated_real const &r, preal_vect const &v)
    : proof_scheme(r, v, "nzr_of_nzr_rel_rev") {}
REGISTER_SCHEME_END_PATTERN(nzr_of_nzr_rel_rev, predicated_real(pattern(-1), PRED_NZR));

void nzr_of_nzr_rel_rev_scheme::compute(property const [], property &, std::string &) const
{
}

proof_scheme *nzr_of_nzr_rel_rev_scheme::factory(predicated_real const &real, ast_real_vect const &holders)
{
  if (holders[0]->is_constant) return NULL;
  preal_vect hyps;
  hyps.push_back(predicated_real(holders[1], PRED_NZR));
  hyps.push_back(predicated_real(holders[1], holders[0], PRED_REL));
  return new nzr_of_nzr_rel_rev_scheme(real, hyps);
}
