// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#include "Sdc.hh"

#include <algorithm>
#include <cstddef>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "Clock.hh"
#include "ClockGatingCheck.hh"
#include "ClockGroups.hh"
#include "ClockInsertion.hh"
#include "ClockLatency.hh"
#include "ContainerHelpers.hh"
#include "CycleAccting.hh"
#include "DataCheck.hh"
#include "Debug.hh"
#include "DeratingFactors.hh"
#include "DisabledPorts.hh"
#include "ExceptionPath.hh"
#include "Format.hh"
#include "Graph.hh"
#include "HpinDrvrLoad.hh"
#include "InputDrive.hh"
#include "Liberty.hh"
#include "LibertyClass.hh"
#include "MinMax.hh"
#include "Mutex.hh"
#include "Network.hh"
#include "NetworkClass.hh"
#include "PatternMatch.hh"
#include "PinPair.hh"
#include "PortDelay.hh"
#include "PortDirection.hh"
#include "PortExtCap.hh"
#include "Report.hh"
#include "RiseFallMinMax.hh"
#include "Scene.hh"
#include "SdcClass.hh"
#include "Stats.hh"
#include "TimingArc.hh"
#include "TimingRole.hh"
#include "Transition.hh"
#include "Variables.hh"
#include "search/Levelize.hh"

namespace sta {

bool
ClockPairLess::operator()(const ClockPair &pair1,
                          const ClockPair &pair2) const
{
  const auto& [clk1_1, clk2_1] = pair1;
  int first1 = clk1_1->index();
  int second1 = clk2_1->index();
  if (first1 > second1)
    std::swap(first1, second1);
  const auto& [clk1_2, clk2_2] = pair2;
  int first2 = clk1_2->index();
  int second2 = clk2_2->index();
  if (first2 > second2)
    std::swap(first2, second2);
  return (first1 < first2)
    || (first1 == first2
        && second1 < second2);
}

using ClockPairSeq = std::vector<ClockPair>;
using PvtSet = std::set<Pvt*>;

static ExceptionThruSeq *
clone(ExceptionThruSeq *thrus,
      Network *network);

////////////////////////////////////////////////////////////////

Sdc::Sdc(Mode *mode,
         StaState *sta) :
  StaState(sta),
  mode_(mode),
  clock_pin_map_(10, PinIdHash(network_)),
  clock_leaf_pin_map_(10, PinIdHash(network_)),
  clk_hpin_disables_(network_),
  propagated_clk_pins_(network_),
  clk_latencies_(network_),
  clk_latency_pins_(network_),
  edge_clk_latency_map_(network_),
  clk_insertions_(network_),
  clk_sense_map_(network_),
  cycle_acctings_(this),

  input_delay_pin_map_(PinIdLess(network_)),
  input_delay_leaf_pin_map_(PinIdLess(network_)),
  input_delay_internal_pin_map_(PinIdLess(network_)),

  output_delay_pin_map_(PinIdLess(network_)),
  output_delay_leaf_pin_map_(PinIdLess(network_)),

  port_ext_cap_map_(network_),
  net_wire_cap_map_(network_),
  drvr_pin_wire_cap_map_(network_),

  disabled_pins_(network_),
  disabled_ports_(network_),
  disabled_wire_edges_(network_),
  disabled_clk_gating_checks_inst_(network_),
  disabled_clk_gating_checks_pin_(network_),
  first_thru_edge_exceptions_(0, PinPairHash(network_), PinPairEqual()),
  path_delay_internal_from_(network_),
  path_delay_internal_from_break_(network_),
  path_delay_internal_to_(network_),
  path_delay_internal_to_break_(network_)
{
  initVariables();
  setWireload(nullptr, MinMaxAll::all());
  setWireloadSelection(nullptr, MinMaxAll::all());
  setOperatingConditions(nullptr, MinMaxAll::all());
  makeDefaultArrivalClock();
}

void
Sdc::makeDefaultArrivalClock()
{
  FloatSeq waveform;
  waveform.push_back(0.0);
  waveform.push_back(0.0);
  default_arrival_clk_ = new Clock("input port clock", clk_index_++, network_);
  default_arrival_clk_->initClk(nullptr, false, 0.0, waveform, "", network_);
}

Sdc::~Sdc()
{
  deleteConstraints();
}

// This does NOT call initVariables() because those variable values
// survive linking a new design.
void
Sdc::clear()
{
  deleteConstraints();
  propagated_clk_pins_.clear();
  clocks_.clear();
  clock_name_map_.clear();
  clock_pin_map_.clear();
  clock_leaf_pin_map_.clear();
  clk_latencies_.clear();
  clk_latency_pins_.clear();
  edge_clk_latency_map_.clear();
  clk_insertions_.clear();

  pin_clk_uncertainty_map_.clear();
  inter_clk_uncertainties_.clear();

  clk_groups_name_map_.clear();
  clearClkGroupExclusions();

  clk_gating_check_map_.clear();
  inst_clk_gating_check_map_.clear();
  pin_clk_gating_check_map_.clear();
  data_checks_from_map_.clear();
  data_checks_to_map_.clear();

  input_delays_.clear();
  input_delay_pin_map_.clear();
  input_delay_index_ = 0;
  input_delay_leaf_pin_map_.clear();
  input_delay_internal_pin_map_.clear();
  have_input_delay_ref_pins_ = false;

  output_delays_.clear();
  output_delay_pin_map_.clear();
  output_delay_leaf_pin_map_.clear();

  port_slew_limit_map_.clear();
  cell_slew_limit_map_.clear();
  have_clk_slew_limits_ = false;

  cell_cap_limit_map_.clear();
  port_cap_limit_map_.clear();
  pin_cap_limit_map_.clear();

  port_fanout_limit_map_.clear();
  cell_fanout_limit_map_.clear();

  disabled_pins_.clear();
  disabled_ports_.clear();
  disabled_lib_ports_.clear();
  disabled_edges_.clear();
  disabled_cell_ports_.clear();
  disabled_inst_ports_.clear();

  disabled_clk_gating_checks_inst_.clear();
  disabled_clk_gating_checks_pin_.clear();

  input_drive_map_.clear();
  logic_value_map_.clear();
  case_value_map_.clear();

  pin_latch_borrow_limit_map_.clear();
  inst_latch_borrow_limit_map_.clear();
  clk_latch_borrow_limit_map_.clear();

  min_pulse_width_.clear();

  setWireload(nullptr, MinMaxAll::all());
  setWireloadSelection(nullptr, MinMaxAll::all());
  // Operating conditions are owned by Liberty libraries.
  setOperatingConditions(nullptr, MinMaxAll::all());
  clk_index_ = 0;
  makeDefaultArrivalClock();

  unsetTimingDerate();
}

void
Sdc::initVariables()
{
  analysis_type_ = AnalysisType::ocv;
  wireload_mode_ = WireloadMode::unknown;
  max_area_ = 0.0;
  path_delays_without_to_ = false;
  clk_hpin_disables_valid_ = false;
  have_clk_slew_limits_ = false;
}

void
Sdc::deleteConstraints()
{
  deleteContents(clocks_);;
  delete default_arrival_clk_;
  deleteContents(clock_pin_map_);
  deleteContents(clock_leaf_pin_map_);
  deleteContents(clk_latencies_);
  deleteContents(clk_insertions_);

  deleteContents(clk_groups_name_map_);
  clearClkGroupExclusions();

  deleteContents(pin_clk_uncertainty_map_);
  deleteContents(inter_clk_uncertainties_);
  delete clk_gating_check_;
  clk_gating_check_ = nullptr;
  deleteContents(clk_gating_check_map_);
  deleteContents(inst_clk_gating_check_map_);
  deleteContents(pin_clk_gating_check_map_);
  deleteContents(input_drive_map_);
  deleteContents(disabled_cell_ports_);
  deleteContents(disabled_inst_ports_);
  deleteContents(pin_min_pulse_width_map_);
  deleteContents(inst_min_pulse_width_map_);
  deleteContents(clk_min_pulse_width_map_);

  for (auto [pin, checks] : data_checks_from_map_) {
    deleteContents(*checks);
    delete checks;
  }
  deleteContents(data_checks_to_map_);

  deleteContents(input_delays_);
  deleteContents(input_delay_pin_map_);
  deleteContents(input_delay_leaf_pin_map_);
  deleteContents(input_delay_internal_pin_map_);

  deleteContents(output_delays_);
  deleteContents(output_delay_pin_map_);
  deleteContents(output_delay_leaf_pin_map_);

  deleteContents(clk_hpin_disables_);
  clk_hpin_disables_valid_ = false;

  clearCycleAcctings();
  deleteExceptions();
  deleteFilter();
  clearGroupPathMap();
  deleteDeratingFactors();

  removeNetLoadCaps();

  clk_sense_map_.clear();

  for (int mm_index : MinMax::rangeIndex())
    deleteContents(instance_pvt_maps_[mm_index]);
}

void
Sdc::searchPreamble()
{
  ensureClkHpinDisables();
  ensureClkGroupExclusions();
}

void
Sdc::removeNetLoadCaps()
{
  net_wire_cap_map_.clear();
  drvr_pin_wire_cap_map_.clear();
  port_ext_cap_map_.clear();
}

void
Sdc::deleteNetBefore(const Net *net)
{
  net_wire_cap_map_.erase(net);
  for (const Pin *pin : *network_->drivers(net))
    drvr_pin_wire_cap_map_.erase(pin);
}

// see Sdc::isConstrained
void
Sdc::deleteInstanceBefore(const Instance *inst)
{
  instance_pvt_maps_[MinMax::minIndex()].erase(inst);
  instance_pvt_maps_[MinMax::maxIndex()].erase(inst);
  inst_derating_factors_.erase(inst);
  inst_clk_gating_check_map_.erase(inst);
  disabled_inst_ports_.erase(inst);
  inst_latch_borrow_limit_map_.erase(inst);
  inst_min_pulse_width_map_.erase(inst);

  for (ExceptionPath *exception : exceptions_)
    exception->deleteInstance(inst, network_);
  first_from_inst_exceptions_.erase(inst);
  first_thru_inst_exceptions_.erase(inst);
  first_to_inst_exceptions_.erase(inst);
}

void
Sdc::makeSceneBefore()
{
  removeNetLoadCaps();
}

////////////////////////////////////////////////////////////////

bool
Sdc::isConstrained(const Pin *pin) const
{
  Port *port = network_->isTopLevelPort(pin) ? network_->port(pin) : nullptr;
  return clock_pin_map_.contains(pin)
    || propagated_clk_pins_.contains(pin)
    || hasClockLatency(pin)
    || hasClockInsertion(pin)
    || pin_clk_uncertainty_map_.contains(pin)
    || pin_clk_gating_check_map_.contains(pin)
    || data_checks_from_map_.contains(pin)
    || data_checks_to_map_.contains(pin)
    || input_delay_pin_map_.contains(pin)
    || output_delay_pin_map_.contains(pin)
    || pin_cap_limit_map_.contains(pin)
    || disabled_pins_.contains(pin)
    || disabled_ports_.contains(port)
    || disabled_clk_gating_checks_pin_.contains(pin)
    || first_from_pin_exceptions_.contains(pin)
    || first_thru_pin_exceptions_.contains(pin)
    || first_to_pin_exceptions_.contains(pin)
    || input_drive_map_.contains(port)
    || logic_value_map_.contains(pin)
    || case_value_map_.contains(pin)
    || pin_latch_borrow_limit_map_.contains(pin)
    || pin_min_pulse_width_map_.contains(pin)
    || (port && (port_slew_limit_map_.contains(port)
                 || port_cap_limit_map_.contains(port)
                 || port_fanout_limit_map_.contains(port)
                 || hasPortExtCap(port)));
}

bool
Sdc::isConstrained(const Instance *inst) const
{
  return instance_pvt_maps_[MinMax::minIndex()].contains(inst)
    || instance_pvt_maps_[MinMax::maxIndex()].contains(inst)
    || inst_derating_factors_.contains(inst)
    || inst_clk_gating_check_map_.contains(inst)
    || disabled_inst_ports_.contains(inst)
    || first_from_inst_exceptions_.contains(inst)
    || first_thru_inst_exceptions_.contains(inst)
    || first_to_inst_exceptions_.contains(inst)
    || inst_latch_borrow_limit_map_.contains(inst)
    || inst_min_pulse_width_map_.contains(inst);
}

bool
Sdc::isConstrained(const Net *net) const
{
  return net_derating_factors_.contains(net)
    || hasNetWireCap(net)
    || net_res_map_.contains(net)
    || first_thru_net_exceptions_.contains(net);
}

////////////////////////////////////////////////////////////////

PortSeq
Sdc::allInputs(bool no_clks) const
{
  PortSeq ports;
  Instance *top_inst = network_->topInstance();
  InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
  while (pin_iter->hasNext()) {
    const Pin *pin = pin_iter->next();
    const Port *port = network_->port(pin);
    PortDirection *dir = network_->direction(port);
    if (dir->isAnyInput()
        && !(no_clks && isClock(pin)))
      portMembers(port, ports);
  }
  delete pin_iter;
  return ports;
}

PortSeq
Sdc::allOutputs() const
{
  PortSeq ports;
  Instance *top_inst = network_->topInstance();
  InstancePinIterator *pin_iter = network_->pinIterator(top_inst);
  while (pin_iter->hasNext()) {
    const Pin *pin = pin_iter->next();
    const Port *port = network_->port(pin);
    PortDirection *dir = network_->direction(port);
    if (dir->isAnyOutput())
      portMembers(port, ports);
  }
  delete pin_iter;
  return ports;
}

void
Sdc::portMembers(const Port *port,
                 PortSeq &ports) const
{
  if (network_->isBus(port)) {
    PortMemberIterator *member_iter = network_->memberIterator(port);
    while (member_iter->hasNext()) {
      Port *member = member_iter->next();
      ports.push_back(member);
    }
    delete member_iter;
  }
  else
    ports.push_back(port);
}

void
Sdc::setAnalysisType(AnalysisType analysis_type)
{
  analysis_type_ = analysis_type;
}

void
Sdc::setOperatingConditions(OperatingConditions *op_cond,
                            const MinMaxAll *min_max)
{
  for (auto mm_index : min_max->rangeIndex())
    operating_conditions_[mm_index] = op_cond;
}

void
Sdc::setOperatingConditions(OperatingConditions *op_cond,
                            const MinMax *min_max)
{
  int mm_index = min_max->index();
  operating_conditions_[mm_index] = op_cond;
}

OperatingConditions *
Sdc::operatingConditions(const MinMax *min_max) const
{
  int mm_index = min_max->index();
  return operating_conditions_[mm_index];
}

const Pvt *
Sdc::pvt(const Instance *inst,
         const MinMax *min_max) const
{
  const InstancePvtMap &pvt_map = instance_pvt_maps_[min_max->index()];
  return findKey(pvt_map, inst);
}

void
Sdc::setPvt(const Instance *inst,
            const MinMaxAll *min_max,
            const Pvt &pvt)
{
  for (auto mm_index : min_max->rangeIndex()) {
    InstancePvtMap &pvt_map = instance_pvt_maps_[mm_index];
    pvt_map[inst] = new Pvt(pvt);
  }
}

void
Sdc::voltage(const MinMax *min_max,
             // Return values.
             float &voltage,
             bool &exists) const
{
  voltages_.value(min_max, voltage, exists);
}

void
Sdc::voltage(const Net *net,
             const MinMax *min_max,
             // Return values.
             float &voltage,
             bool &exists) const
{
  exists = false;
  auto itr = net_voltage_map_.find(net);
  if (itr != net_voltage_map_.end()) {
    const MinMaxFloatValues &values = itr->second;
    values.value(min_max, voltage, exists);
  }
}

void
Sdc::setVoltage(const MinMax *min_max,
                float voltage)
{
  voltages_.setValue(min_max, voltage);
}

void
Sdc::setVoltage(const Net *net,
                const MinMax *min_max,
                float voltage)
{
  net_voltage_map_[net].setValue(min_max, voltage);
}

////////////////////////////////////////////////////////////////

void
Sdc::setTimingDerate(TimingDerateType type,
                     PathClkOrData clk_data,
                     const RiseFallBoth *rf,
                     const EarlyLate *early_late,
                     float derate)
{
  if (derating_factors_ == nullptr)
    derating_factors_ = new DeratingFactorsGlobal;
  derating_factors_->setFactor(type, clk_data, rf, early_late, derate);
}

void
Sdc::setTimingDerate(const Net *net,
                     PathClkOrData clk_data,
                     const RiseFallBoth *rf,
                     const EarlyLate *early_late,
                     float derate)
{
  DeratingFactors *factors = findKey(net_derating_factors_, net);
  if (factors == nullptr) { 
    factors = new DeratingFactors;
    net_derating_factors_[net] = factors;
  }
  factors->setFactor(clk_data, rf, early_late, derate);
}

void
Sdc::setTimingDerate(const Instance *inst,
                     TimingDerateCellType type,
                     PathClkOrData clk_data,
                     const RiseFallBoth *rf,
                     const EarlyLate *early_late,
                     float derate)
{
  DeratingFactorsCell *factors = findKey(inst_derating_factors_, inst);
  if (factors == nullptr) {
    factors = new DeratingFactorsCell;
    inst_derating_factors_[inst] = factors;
  }
  factors->setFactor(type, clk_data, rf, early_late, derate);
}

void
Sdc::setTimingDerate(const LibertyCell *cell,
                     TimingDerateCellType type,
                     PathClkOrData clk_data,
                     const RiseFallBoth *rf,
                     const EarlyLate *early_late,
                     float derate)
{
  DeratingFactorsCell *factors = findKey(cell_derating_factors_, cell);
  if (factors == nullptr) {
    factors = new DeratingFactorsCell;
    cell_derating_factors_[cell] = factors;
  }
  factors->setFactor(type, clk_data, rf, early_late, derate);
}

float
Sdc::timingDerateInstance(const Pin *pin,
                          TimingDerateCellType type,
                          PathClkOrData clk_data,
                          const RiseFall *rf,
                          const EarlyLate *early_late) const
{
  const Instance *inst = network_->instance(pin);
  DeratingFactorsCell *factors = findKey(inst_derating_factors_, inst);
  if (factors) {
    float factor;
    bool exists;
    factors->factor(type, clk_data, rf, early_late, factor, exists);
    if (exists)
      return factor;
  }

  const LibertyCell *cell = network_->libertyCell(inst);
  if (cell) {
    DeratingFactorsCell *factors = findKey(cell_derating_factors_, cell);
    float factor;
    bool exists;
    if (factors) {
      factors->factor(type, clk_data, rf, early_late, factor, exists);
      if (exists)
        return factor;
    }
  }

  if (derating_factors_) {
    float factor;
    bool exists;
    derating_factors_->factor(type, clk_data, rf, early_late, factor, exists);
    if (exists)
      return factor;
  }
  return 1.0;
}

float
Sdc::timingDerateNet(const Pin *pin,
                     PathClkOrData clk_data,
                     const RiseFall *rf,
                     const EarlyLate *early_late) const
{
  const Net *net = network_->net(pin);
  DeratingFactors *factors = findKey(net_derating_factors_, net);
  if (factors) {
    float factor;
    bool exists;
    factors->factor(clk_data, rf, early_late, factor, exists);
    if (exists)
      return factor;
  }

  if (derating_factors_) {
    float factor;
    bool exists;
    derating_factors_->factor(TimingDerateType::net_delay, clk_data, rf,
                              early_late, factor, exists);
    if (exists)
      return factor;
  }
  return 1.0;
}

void
Sdc::unsetTimingDerate()
{
  deleteDeratingFactors();
}

void
Sdc::swapDeratingFactors(Sdc *sdc1,
                         Sdc *sdc2)
{
  std::swap(sdc1->derating_factors_, sdc2->derating_factors_);
  std::swap(sdc1->net_derating_factors_, sdc2->net_derating_factors_);
  std::swap(sdc1->inst_derating_factors_, sdc2->inst_derating_factors_);
  std::swap(sdc1->cell_derating_factors_, sdc2->cell_derating_factors_);
}

void
Sdc::deleteDeratingFactors()
{
  deleteContents(net_derating_factors_);
  deleteContents(inst_derating_factors_);
  deleteContents(cell_derating_factors_);

  delete derating_factors_;
  derating_factors_ = nullptr;
}

////////////////////////////////////////////////////////////////

void
Sdc::setDriveCell(const LibertyLibrary *library,
                  const LibertyCell *cell,
                  const Port *port,
                  const LibertyPort *from_port,
                  const DriveCellSlews &from_slews,
                  const LibertyPort *to_port,
                  const RiseFallBoth *rf,
                  const MinMaxAll *min_max)
{
  ensureInputDrive(port)->setDriveCell(library, cell, from_port, from_slews,
                                       to_port, rf, min_max);
}

void
Sdc::setInputSlew(const Port *port,
                  const RiseFallBoth *rf,
                  const MinMaxAll *min_max,
                  float slew)
{
  ensureInputDrive(port)->setSlew(rf, min_max, slew);
}

void
Sdc::setDriveResistance(const Port *port,
                        const RiseFallBoth *rf,
                        const MinMaxAll *min_max,
                        float res)
{
  ensureInputDrive(port)->setDriveResistance(rf, min_max, res);
}

InputDrive *
Sdc::ensureInputDrive(const Port *port)
{
  InputDrive *drive = findKey(input_drive_map_, port);
  if (drive == nullptr) {
    drive = new InputDrive;
    input_drive_map_[port] = drive;
  }
  return drive;
}

////////////////////////////////////////////////////////////////

void
Sdc::setSlewLimit(Clock *clk,
                  const RiseFallBoth *rf,
                  PathClkOrData clk_data,
                  const MinMax *min_max,
                  float slew)
{
  clk->setSlewLimit(rf, clk_data, min_max, slew);
  have_clk_slew_limits_ = true;
}

bool
Sdc::haveClkSlewLimits() const
{
  return have_clk_slew_limits_;
}

void
Sdc::slewLimit(const Clock *clk,
               const RiseFall *rf,
               PathClkOrData clk_data,
               const MinMax *min_max,
               float &slew,
               bool &exists) const
{
  clk->slewLimit(rf, clk_data, min_max, slew, exists);
}

void
Sdc::slewLimit(Port *port,
               const MinMax *min_max,
               float &slew,
               bool &exists) const
{
  slew = INF;
  MinMaxFloatValues values;
  findKeyValue(port_slew_limit_map_, port, values, exists);
  values.value(min_max, slew, exists);
}

void
Sdc::setSlewLimit(Port *port,
                  const MinMax *min_max,
                  float slew)
{
  MinMaxFloatValues &values = port_slew_limit_map_[port];
  values.setValue(min_max, slew);
}

void
Sdc::slewLimit(Cell *cell,
               const MinMax *min_max,
               float &slew,
               bool &exists) const
{
  slew = INF;
  MinMaxFloatValues values;
  findKeyValue(cell_slew_limit_map_, cell, values, exists);
  if (exists)
    values.value(min_max, slew, exists);
}

void
Sdc::setSlewLimit(Cell *cell,
                  const MinMax *min_max,
                  float slew)
{
  MinMaxFloatValues &values = cell_slew_limit_map_[cell];
  values.setValue(min_max, slew);
}

void
Sdc::capacitanceLimit(Cell *cell,
                      const MinMax *min_max,
                      float &cap,
                      bool &exists) const
{
  cap = 0.0;
  exists = false;
  MinMaxFloatValues values;
  findKeyValue(cell_cap_limit_map_, cell, values, exists);
  if (exists)
    values.value(min_max, cap, exists);
}

void
Sdc::setCapacitanceLimit(Cell *cell,
                         const MinMax *min_max,
                         float cap)
{
  MinMaxFloatValues &values = cell_cap_limit_map_[cell];
  values.setValue(min_max, cap);
}

void
Sdc::capacitanceLimit(Port *port,
                      const MinMax *min_max,
                      float &cap,
                      bool &exists) const
{
  cap = 0.0;
  exists = false;
  MinMaxFloatValues values;
  findKeyValue(port_cap_limit_map_, port, values, exists);
  if (exists)
    values.value(min_max, cap, exists);
}

void
Sdc::setCapacitanceLimit(Port *port,
                         const MinMax *min_max,
                         float cap)
{
  MinMaxFloatValues &values = port_cap_limit_map_[port];
  values.setValue(min_max, cap);
}

void
Sdc::capacitanceLimit(Pin *pin,
                      const MinMax *min_max,
                      float &cap,
                      bool &exists) const
{
  cap = 0.0;
  exists = false;
  MinMaxFloatValues values;
  findKeyValue(pin_cap_limit_map_, pin, values, exists);
  if (exists)
    values.value(min_max, cap, exists);
}

void
Sdc::setCapacitanceLimit(Pin *pin,
                         const MinMax *min_max,
                         float cap)
{
  MinMaxFloatValues &values = pin_cap_limit_map_[pin];
  values.setValue(min_max, cap);
}

void
Sdc::fanoutLimit(Cell *cell,
                 const MinMax *min_max,
                 float &fanout,
                 bool &exists) const
{
  fanout = min_max->initValue();
  MinMaxFloatValues values;
  findKeyValue(cell_fanout_limit_map_, cell, values, exists);
  if (exists)
    values.value(min_max, fanout, exists);
}

void
Sdc::setFanoutLimit(Cell *cell,
                    const MinMax *min_max,
                    float fanout)
{
  MinMaxFloatValues &values = cell_fanout_limit_map_[cell];
  values.setValue(min_max, fanout);
}

void
Sdc::fanoutLimit(Port *port,
                 const MinMax *min_max,
                 float &fanout,
                 bool &exists) const
{
  fanout = 0.0;
  MinMaxFloatValues values;
  findKeyValue(port_fanout_limit_map_, port, values, exists);
  if (exists)
    values.value(min_max, fanout, exists);
}

void
Sdc::setFanoutLimit(Port *port,
                    const MinMax *min_max,
                    float fanout)
{
  MinMaxFloatValues &values = port_fanout_limit_map_[port];
  values.setValue(min_max, fanout);
}

void
Sdc::setMaxArea(float area)
{
  max_area_ = area;
}

float
Sdc::maxArea() const
{
  return max_area_;
}

////////////////////////////////////////////////////////////////

Clock *
Sdc::makeClock(std::string_view name,
               const PinSet &pins,
               bool add_to_pins,
               float period,
               const FloatSeq &waveform,
               std::string_view comment)
{
  Clock *clk = findStringKey(clock_name_map_, name);
  if (!add_to_pins)
    deletePinClocks(clk, pins);
  if (clk)
    // Named clock redefinition.
    deleteClkPinMappings(clk);
  else {
    // Fresh clock definition.
    clk = new Clock(name, clk_index_++, network_);
    clk->setIsPropagated(variables_->propagateAllClocks());
    clocks_.push_back(clk);
    // Use the copied name in the map.
    clock_name_map_[clk->name()] = clk;
  }
  clk->initClk(pins, add_to_pins, period, waveform, comment, network_);
  makeClkPinMappings(clk);
  clearCycleAcctings();
  invalidateGeneratedClks();
  clkHpinDisablesInvalid();
  return clk;
}

Clock *
Sdc::makeGeneratedClock(std::string_view name,
                        const PinSet &pins,
                        bool add_to_pins,
                        Pin *src_pin,
                        Clock *master_clk,
                        int divide_by,
                        int multiply_by,
                        float duty_cycle,
                        bool invert,
                        bool combinational,
                        const IntSeq &edges,
                        const FloatSeq &edge_shifts,
                        std::string_view comment)
{
  Clock *clk = findStringKey(clock_name_map_, name);
  if (!add_to_pins)
    deletePinClocks(clk, pins);
  if (clk)
    deleteClkPinMappings(clk);
  else {
    clk = new Clock(name, clk_index_++, network_);
    clocks_.push_back(clk);
    clock_name_map_[clk->name()] = clk;
  }
  clk->initGeneratedClk(pins, add_to_pins, src_pin, master_clk,
                        divide_by, multiply_by, duty_cycle,
                        invert, combinational,
                        edges, edge_shifts,
                        variables_->propagateAllClocks(),
                        comment, network_);
  makeClkPinMappings(clk);
  clearCycleAcctings();
  invalidateGeneratedClks();
  clkHpinDisablesInvalid();
  return clk;
}

void
Sdc::invalidateGeneratedClks() const
{
  for (auto clk : clocks_) {
    if (clk->isGenerated())
      clk->waveformInvalid();
  }
}

// If the clock is not defined with the -add option, any pins that already
// have a clock attached to them are removed from the pin.  If the clock
// is not the clock being defined and has no pins it is removed.
void
Sdc::deletePinClocks(Clock *defining_clk,
                     const PinSet &pins)
{
  // Find all the clocks defined on pins to avoid finding the clock's
  // vertex pins multiple times.
  ClockSet clks;
  for (const Pin *pin : pins) {
    ClockSet *pin_clks = findKey(clock_pin_map_, pin);
    if (pin_clks) {
      for (Clock *clk : *pin_clks) 
        clks.insert(clk);
    }
  }
  for (Clock *clk : clks) {
    deleteClkPinMappings(clk);
    for (const Pin *pin : pins)
      clk->deletePin(pin);
    if (clk != defining_clk) {
      if (clk->pins().empty())
        removeClock(clk);
      else {
        clk->makeLeafPins(network_);
        // One of the remaining clock pins may use a vertex pin that
        // was deleted above.
        makeClkPinMappings(clk);
      }
    }
  }
}

void
Sdc::deleteClkPinMappings(Clock *clk)
{
  for (const Pin *pin : clk->pins()) {
    ClockSet *pin_clks = findKey(clock_pin_map_, pin);
    if (pin_clks) {
      pin_clks->erase(clk);
      if (pin_clks->empty()) {
        clock_pin_map_.erase(pin);
        delete pin_clks;
      }
    }
  }

  for (const Pin *pin : clk->leafPins()) {
    ClockSet *pin_clks = findKey(clock_leaf_pin_map_, pin);
    if (pin_clks) {
      pin_clks->erase(clk);
      if (pin_clks->empty()) {
        clock_leaf_pin_map_.erase(pin);
        delete pin_clks;
      }
    }
  }
}

void
Sdc::makeClkPinMappings(Clock *clk)
{
  for (const Pin *pin : clk->pins()) {
    ClockSet *pin_clks = findKey(clock_pin_map_, pin);
    if (pin_clks == nullptr) {
      pin_clks = new ClockSet;
      clock_pin_map_[pin] =  pin_clks;
    }
    pin_clks->insert(clk);
  }

  for (const Pin *pin : clk->leafPins()) {
    ClockSet *pin_clks = findKey(clock_leaf_pin_map_, pin);
    if (pin_clks == nullptr) {
      pin_clks = new ClockSet;
      clock_leaf_pin_map_[pin] = pin_clks;
    }
    pin_clks->insert(clk);
  }
}

void
Sdc::removeClock(Clock *clk)
{
  deleteExceptionsReferencing(clk);
  deleteInputDelaysReferencing(clk);
  deleteOutputDelaysReferencing(clk);
  deleteClockLatenciesReferencing(clk);
  deleteClockInsertionsReferencing(clk);
  deleteInterClockUncertaintiesReferencing(clk);
  deleteLatchBorrowLimitsReferencing(clk);
  deleteMinPulseWidthReferencing(clk);
  deleteMasterClkRefs(clk);
  clockGroupsDeleteClkRefs(clk);
  clearCycleAcctings();

  deleteClkPinMappings(clk);
  clocks_.erase(std::ranges::find(clocks_, clk));
  clock_name_map_.erase(clk->name());
  delete clk;
}

// Delete references to clk as a master clock.
void
Sdc::deleteMasterClkRefs(Clock *clk)
{
  for (auto gclk : clocks_) {
    if (gclk->isGenerated()
        && gclk->masterClk() == clk) {
      gclk->setMasterClk(nullptr);
    }
  }
}

Clock *
Sdc::findClock(std::string_view name) const
{
  return findStringKey(clock_name_map_, name);
}

bool
Sdc::isClock(const Pin *pin) const
{
  ClockSet *clks = findClocks(pin);
  return clks && !clks->empty();
}

bool
Sdc::isLeafPinClock(const Pin *pin) const
{
  ClockSet *clks = findLeafPinClocks(pin);
  return clks && !clks->empty();
}

bool
Sdc::isLeafPinNonGeneratedClock(const Pin *pin) const
{
  ClockSet *clks = findLeafPinClocks(pin);
  if (clks) {
    for (Clock *clk : *clks) {
      if (!clk->isGenerated())
        return true;
    }
    return false;
  }
  else
    return false;
}

ClockSet *
Sdc::findLeafPinClocks(const Pin *pin) const
{
  return findKey(clock_leaf_pin_map_, pin);
}

ClockSet *
Sdc::findClocks(const Pin *pin) const
{
  return findKey(clock_pin_map_, pin);
}

ClockSeq
Sdc::findClocksMatching(PatternMatch *pattern) const
{
  ClockSeq matches;
  if (!pattern->hasWildcards()) {
    Clock *clk = findClock(pattern->pattern());
    if (clk)
      matches.push_back(clk);
  }
  else {
    for (auto clk : clocks_) {
      if (pattern->match(clk->name()))
        matches.push_back(clk);
    }
  }
  return matches;
}

ClockSeq
Sdc::sortedClocks() const
{
  ClockSeq clks;
  for (Clock *clk : clocks_)
    clks.push_back(clk);
  sort(clks, ClockNameLess());
  return clks;
}

ClockEdge *
Sdc::defaultArrivalClockEdge() const
{
  return default_arrival_clk_->edge(RiseFall::rise());
}

////////////////////////////////////////////////////////////////

class ClkHpinDisable
{
public:
  ClkHpinDisable(const Clock *clk,
                 const Pin *from_pin,
                 const Pin *to_pin);
  const Clock *clk() const { return clk_; }
  const Pin *fromPin() const { return from_pin_; }
  const Pin *toPin() const { return to_pin_; }

private:
  const Clock *clk_;
  const Pin *from_pin_;
  const Pin *to_pin_;
};

ClkHpinDisable::ClkHpinDisable(const Clock *clk,
                               const Pin *from_pin,
                               const Pin *to_pin) :
  clk_(clk),
  from_pin_(from_pin),
  to_pin_(to_pin)
{
}

ClkHpinDisableLess::ClkHpinDisableLess(const Network *network) :
  network_(network)
{
}

bool
ClkHpinDisableLess::operator()(const ClkHpinDisable *disable1,
                               const ClkHpinDisable *disable2) const
{
  int clk_index1 = disable1->clk()->index();
  int clk_index2 = disable2->clk()->index();
  const Pin *from_pin1 = disable1->fromPin();
  const Pin *from_pin2 = disable2->fromPin();
  const Pin *to_pin1 = disable1->toPin();
  const Pin *to_pin2 = disable2->toPin();
  return clk_index1 < clk_index2
    || (clk_index1 == clk_index2
        && (network_->id(to_pin1) < network_->id(to_pin2)
            || (from_pin1 == from_pin2
                && network_->id(from_pin1) < network_->id(from_pin2))));
}

class FindClkHpinDisables : public HpinDrvrLoadVisitor
{
public:
  FindClkHpinDisables(Clock *clk,
                      const Network *network,
                      Sdc *sdc);
  bool drvrLoadExists(const Pin *drvr,
                      const Pin *load);
  void visit(HpinDrvrLoad *drvr_load) override;

protected:
  void makeClkHpinDisables(const Pin *clk_src,
                           const Pin *drvr,
                           const Pin *load);

  Clock *clk_;
  PinPairSet drvr_loads_;
  const Network *network_;
  Sdc *sdc_;
};

FindClkHpinDisables::FindClkHpinDisables(Clock *clk,
                                         const Network *network,
                                         Sdc *sdc) :
  clk_(clk),
  drvr_loads_(network),
  network_(network),
  sdc_(sdc)
{
}

void
FindClkHpinDisables::visit(HpinDrvrLoad *drvr_load)
{
  const Pin *drvr = drvr_load->drvr();
  const Pin *load = drvr_load->load();

  makeClkHpinDisables(drvr, drvr, load);

  PinSet *hpins_from_drvr = drvr_load->hpinsFromDrvr();
  for (const Pin *hpin : *hpins_from_drvr)
    makeClkHpinDisables(hpin, drvr, load);
  drvr_loads_.insert(PinPair(drvr, load));
}

void
FindClkHpinDisables::makeClkHpinDisables(const Pin *clk_src,
                                         const Pin *drvr,
                                         const Pin *load)
{
  ClockSet *clks = sdc_->findClocks(clk_src);
  if (clks) {
    for (Clock *clk : *clks) {
      if (clk != clk_)
        // Do not propagate clock from source pin if another
        // clock is defined on a hierarchical pin between the
        // driver and load.
        sdc_->makeClkHpinDisable(clk, drvr, load);
    }
  }
}

bool
FindClkHpinDisables::drvrLoadExists(const Pin *drvr,
                                    const Pin *load)
{
  return drvr_loads_.contains({drvr, load});
}

void
Sdc::ensureClkHpinDisables()
{
  if (!clk_hpin_disables_valid_) {
    deleteContents(clk_hpin_disables_);
    for (auto clk : clocks_) {
      for (const Pin *src : clk->pins()) {
        if (network_->isHierarchical(src)) {
          FindClkHpinDisables visitor1(clk, network_, this);
          visitHpinDrvrLoads(src, network_, &visitor1);
          PinSeq loads, drvrs;
          PinSet visited_drvrs(network_);
          FindNetDrvrLoads visitor2(nullptr, visited_drvrs, loads, drvrs, network_);
          network_->visitConnectedPins(src, visitor2);

          // Disable fanouts from the src driver pins that do
          // not go thru the hierarchical src pin.
          for (const Pin *drvr : drvrs) {
            for (const Pin *load : loads) {
              if (!visitor1.drvrLoadExists(drvr, load))
                makeClkHpinDisable(clk, drvr, load);
            }
          }
        }
      }
    }
    clk_hpin_disables_valid_ = true;
  }
}

void
Sdc::makeClkHpinDisable(const Clock *clk,
                        const Pin *drvr,
                        const Pin *load)
{
  ClkHpinDisable probe(clk, drvr, load);
  if (!clk_hpin_disables_.contains(&probe)) {
    ClkHpinDisable *disable = new ClkHpinDisable(clk, drvr, load);
    clk_hpin_disables_.insert(disable);
  }
}

void
Sdc::clkHpinDisablesInvalid()
{
  clk_hpin_disables_valid_ = false;
  for (auto clk : clocks_)
    clk->makeLeafPins(network_);
}

// Check that driver/load edge goes thru clock hpin.
// Check for disable by hierarchical clock pin between driver and load.
bool
Sdc::clkDisabledByHpinThru(const Clock *clk,
                           const Pin *from_pin,
                           const Pin *to_pin) const
{
  if (clk->leafPins().contains(from_pin)) {
    ClkHpinDisable probe(clk, from_pin, to_pin);
    return clk_hpin_disables_.contains(&probe);
  }
  else
    return false;
}

////////////////////////////////////////////////////////////////

void
Sdc::setPropagatedClock(Clock *clk)
{
  clk->setIsPropagated(true);
  removeClockLatency(clk, nullptr);
}

void
Sdc::removePropagatedClock(Clock *clk)
{
  clk->setIsPropagated(false);
}

void
Sdc::setPropagatedClock(Pin *pin)
{
  propagated_clk_pins_.insert(pin);
  removeClockLatency(nullptr, pin);
}

void
Sdc::removePropagatedClock(Pin *pin)
{
  propagated_clk_pins_.erase(pin);
}

bool
Sdc::isPropagatedClock(const Pin *pin) const
{
  return propagated_clk_pins_.contains(pin);
}

void
Sdc::setClockSlew(Clock *clk,
                  const RiseFallBoth *rf,
                  const MinMaxAll *min_max,
                  float slew)
{
  clk->setSlew(rf, min_max, slew);
}

void
Sdc::removeClockSlew(Clock *clk)
{
  clk->removeSlew();
}

class MakeClkLatencyEdge : public HierPinThruVisitor
{
public:
  MakeClkLatencyEdge(ClockLatency *latency,
                     EdgeClockLatencyMap &edge_clk_latency_map);
  void visit(const Pin *drvr,
             const Pin *load) override;

private:
  ClockLatency *latency_;
  EdgeClockLatencyMap &edge_clk_latency_map_;
};

MakeClkLatencyEdge::MakeClkLatencyEdge(ClockLatency *latency,
                                       EdgeClockLatencyMap &edge_clk_latency_map) :
  latency_(latency),
  edge_clk_latency_map_(edge_clk_latency_map)
{
}

void
MakeClkLatencyEdge::visit(const Pin *drvr,
                          const Pin *load)
{
  edge_clk_latency_map_[{drvr, load}] = latency_;
}

void
Sdc::setClockLatency(Clock *clk,
                     Pin *pin,
                     const RiseFallBoth *rf,
                     const MinMaxAll *min_max,
                     float delay)
{
  ClockLatency probe(clk, pin);
  ClockLatency *latency = findKey(clk_latencies_, &probe);
  if (latency == nullptr) {
    latency = new ClockLatency(clk, pin);
    clk_latencies_.insert(latency);
    if (pin && network_->isHierarchical(pin)) {
      MakeClkLatencyEdge visitor(latency, edge_clk_latency_map_);
      visitDrvrLoadsThruHierPin(pin, network_, &visitor);
    }
  }
  latency->setDelay(rf, min_max, delay);
  if (pin)
    clk_latency_pins_.insert(pin);

  // set_clock_latency removes set_propagated_clock on the same object.
  if (clk && pin == nullptr)
    removePropagatedClock(clk);
  if (pin)
    removePropagatedClock(pin);
}

ClockLatency *
Sdc::clockLatency(Edge *edge) const
{
  PinPair pins(edge->from(graph_)->pin(), edge->to(graph_)->pin());
  auto itr = edge_clk_latency_map_.find(pins);
  if (itr == edge_clk_latency_map_.end())
    return nullptr;
  else
    return itr->second;
}

void
Sdc::clockLatency(Edge *edge,
                  const RiseFall *rf,
                  const MinMax *min_max,
                  // Return values.
                  float &latency,
                  bool &exists) const

{
  ClockLatency *latencies = clockLatency(edge);
  if (latencies)
    latencies->delay(rf, min_max, latency, exists);
  else {
    latency = 0.0;
    exists = false;
  }
}

void
Sdc::removeClockLatency(const Clock *clk,
                        const Pin *pin)
{
  ClockLatency probe(clk, pin);
  ClockLatency *latency = findKey(clk_latencies_, &probe);
  if (latency)
    deleteClockLatency(latency);
}

void
Sdc::deleteClockLatency(ClockLatency *latency)
{
  clk_latencies_.erase(latency);
  delete latency;
}

void
Sdc::deleteClockLatenciesReferencing(Clock *clk)
{
  for (auto iter = clk_latencies_.cbegin();
       iter != clk_latencies_.cend(); ) {
    ClockLatency *latency = *iter;
    if (latency->clock() == clk) {
      iter = clk_latencies_.erase(iter);
      delete latency;
    }
    else
      iter++;
  }
}

bool
Sdc::hasClockLatency(const Pin *pin) const
{
  return clk_latency_pins_.contains(pin);
}

void
Sdc::clockLatency(const Clock *clk,
                  const Pin *pin,
                  const RiseFall *rf,
                  const MinMax *min_max,
                  // Return values.
                  float &latency,
                  bool &exists) const
{
  latency = 0.0;
  exists = false;
  if (pin && clk) {
    ClockLatency probe(clk, pin);
    ClockLatency *latencies = findKey(clk_latencies_, &probe);
    if (latencies)
      latencies->delay(rf, min_max, latency, exists);
  }
  if (!exists) {
    ClockLatency probe(nullptr, pin);
    ClockLatency *latencies = findKey(clk_latencies_, &probe);
    if (latencies)
      latencies->delay(rf, min_max, latency, exists);
  }
}

void
Sdc::clockLatency(const Clock *clk,
                  const RiseFall *rf,
                  const MinMax *min_max,
                  // Return values.
                  float &latency,
                  bool &exists) const
{
  latency = 0.0;
  exists = false;
  ClockLatency probe(clk, nullptr);
  ClockLatency *latencies = findKey(clk_latencies_, &probe);
  if (latencies)
    latencies->delay(rf, min_max, latency, exists);
}

float
Sdc::clockLatency(const Clock *clk,
                  const RiseFall *rf,
                  const MinMax *min_max) const
{
  float latency;
  bool exists;
  clockLatency(clk, rf, min_max,
               latency, exists);
  return latency;
}

void
Sdc::setClockUncertainty(Pin *pin,
                         const SetupHoldAll *setup_hold,
                         float uncertainty)
{
  ClockUncertainties *uncertainties = findKey(pin_clk_uncertainty_map_, pin);
  if (uncertainties == nullptr) {
    uncertainties = new ClockUncertainties;
    pin_clk_uncertainty_map_[pin] = uncertainties;
  }
  uncertainties->setValue(setup_hold, uncertainty);
}

void
Sdc::removeClockUncertainty(Pin *pin,
                            const SetupHoldAll *setup_hold)
{
  ClockUncertainties *uncertainties = findKey(pin_clk_uncertainty_map_, pin);
  if (uncertainties) {
    uncertainties->removeValue(setup_hold);
    if (uncertainties->empty()) {
      delete uncertainties;
      pin_clk_uncertainty_map_.erase(pin);
    }
  }
}

const ClockUncertainties *
Sdc::clockUncertainties(const Pin *pin) const
{
  return findKey(pin_clk_uncertainty_map_, pin);
}

void
Sdc::clockUncertainty(const Pin *pin,
                      const SetupHold *setup_hold,
                      float &uncertainty,
                      bool &exists)
{
  const ClockUncertainties *uncertainties = clockUncertainties(pin);
  if (uncertainties)
    uncertainties->value(setup_hold, uncertainty, exists);
  else {
    uncertainty = 0.0;
    exists = false;
  }
}

void
Sdc::clockUncertainty(const Clock *src_clk,
                      const RiseFall *src_rf,
                      const Clock *tgt_clk,
                      const RiseFall *tgt_rf,
                      const SetupHold *setup_hold,
                      float &uncertainty,
                      bool &exists) const
{
  InterClockUncertainty probe(src_clk, tgt_clk);
  InterClockUncertainty *uncertainties =
    findKey(inter_clk_uncertainties_, &probe);
  if (uncertainties)
    uncertainties->uncertainty(src_rf, tgt_rf, setup_hold,
                               uncertainty, exists);
  else {
    uncertainty = 0.0;
    exists = false;
  }
}

void
Sdc::setClockUncertainty(Clock *from_clk,
                         const RiseFallBoth *from_rf,
                         Clock *to_clk,
                         const RiseFallBoth *to_rf,
                         const SetupHoldAll *setup_hold,
                         float uncertainty)
{
  InterClockUncertainty probe(from_clk, to_clk);
  InterClockUncertainty *uncertainties =
    findKey(inter_clk_uncertainties_, &probe);
  if (uncertainties == nullptr) {
    uncertainties = new InterClockUncertainty(from_clk, to_clk);
    inter_clk_uncertainties_.insert(uncertainties);
  }
  uncertainties->setUncertainty(from_rf, to_rf, setup_hold, uncertainty);
}

void
Sdc::removeClockUncertainty(Clock *from_clk,
                            const RiseFallBoth *from_rf,
                            Clock *to_clk,
                            const RiseFallBoth *to_rf,
                            const SetupHoldAll *setup_hold)
{
  InterClockUncertainty probe(from_clk, to_clk);
  InterClockUncertainty *uncertainties =
    findKey(inter_clk_uncertainties_, &probe);
  if (uncertainties) {
    uncertainties->removeUncertainty(from_rf, to_rf, setup_hold);
    if (uncertainties->empty()) {
      inter_clk_uncertainties_.erase(uncertainties);
      delete uncertainties;
    }
  }
}

void
Sdc::deleteInterClockUncertainty(InterClockUncertainty *uncertainties)
{
  inter_clk_uncertainties_.erase(uncertainties);
  delete uncertainties;
}

void
Sdc::deleteInterClockUncertaintiesReferencing(Clock *clk)
{
  for (auto iter = inter_clk_uncertainties_.cbegin();
       iter != inter_clk_uncertainties_.cend(); ) {
    InterClockUncertainty *uncertainties = *iter;
    if (uncertainties->src() == clk
        || uncertainties->target() == clk) {
      iter = inter_clk_uncertainties_.erase(iter);
      delete uncertainties;
    }
    else
      iter++;
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::setClockInsertion(const Clock *clk,
                       const Pin *pin,
                       const RiseFallBoth *rf,
                       const MinMaxAll *min_max,
                       const EarlyLateAll *early_late,
                       float delay)
{
  ClockInsertion probe(clk, pin);
  ClockInsertion *insertion = findKey(clk_insertions_, &probe);
  if (insertion == nullptr) {
    insertion = new ClockInsertion(clk, pin);
    clk_insertions_.insert(insertion);
  }
  insertion->setDelay(rf, min_max, early_late, delay);
}

void
Sdc::setClockInsertion(const Clock *clk,
                       const Pin *pin,
                       const RiseFall *rf,
                       const MinMax *min_max,
                       const EarlyLate *early_late,
                       float delay)
{
  ClockInsertion probe(clk, pin);
  ClockInsertion *insertion = findKey(clk_insertions_, &probe);
  if (insertion == nullptr) {
    insertion = new ClockInsertion(clk, pin);
    clk_insertions_.insert(insertion);
  }
  insertion->setDelay(rf, min_max, early_late, delay);
}

void
Sdc::removeClockInsertion(const Clock *clk,
                          const Pin *pin)
{
  ClockInsertion probe(clk, pin);
  ClockInsertion *insertion = findKey(clk_insertions_, &probe);
  if (insertion != nullptr)
    deleteClockInsertion(insertion);
}

void
Sdc::swapClockInsertions(Sdc *sdc1,
                         Sdc *sdc2)
{
  std::swap(sdc1->clk_insertions_, sdc2->clk_insertions_);
}

void
Sdc::deleteClockInsertion(ClockInsertion *insertion)
{
  clk_insertions_.erase(insertion);
  delete insertion;
}

void
Sdc::deleteClockInsertionsReferencing(Clock *clk)
{
  for (auto iter = clk_insertions_.cbegin();
       iter != clk_insertions_.cend(); ) {
    ClockInsertion *insertion = *iter;
    if (insertion->clock() == clk) {
      iter = clk_insertions_.erase(iter);
      delete insertion;
    }
    else
      iter++;
  }
}

float
Sdc::clockInsertion(const Clock *clk,
                    const RiseFall *rf,
                    const MinMax *min_max,
                    const EarlyLate *early_late) const
{
  float insertion;
  bool exists;
  clockInsertion(clk, nullptr, rf, min_max, early_late, insertion, exists);
  return insertion;
}

bool
Sdc::hasClockInsertion(const Pin *pin) const
{
  ClockInsertion probe(nullptr, pin);
  return clk_insertions_.contains(&probe);
}

void
Sdc::clockInsertion(const Clock *clk,
                    const Pin *pin,
                    const RiseFall *rf,
                    const MinMax *min_max,
                    const EarlyLate *early_late,
                    // Return values.
                    float &insertion,
                    bool &exists) const
{
  ClockInsertion *insert = nullptr;
  if (clk && pin) {
    ClockInsertion probe(clk, pin);
    insert = findKey(clk_insertions_, &probe);
  }
  if (insert == nullptr && pin) {
    ClockInsertion probe(nullptr, pin);
    insert = findKey(clk_insertions_, &probe);
  }
  if (insert == nullptr && clk) {
    ClockInsertion probe(clk, nullptr);
    insert = findKey(clk_insertions_, &probe);
  }

  if (insert)
    insert->delay(rf, min_max, early_late, insertion, exists);
  else {
    insertion = 0.0;
    exists = false;
  }
}

////////////////////////////////////////////////////////////////

ClockLatencyLess::ClockLatencyLess(const Network *network) :
  network_(network)
{
}

bool
ClockLatencyLess::operator()(const ClockLatency *latency1,
                             const ClockLatency *latency2) const
{
  const Clock *clk1 = latency1->clock();
  const Clock *clk2 = latency2->clock();
  const Pin *pin1 = latency1->pin();
  const Pin *pin2 = latency2->pin();
  return (clk1 == nullptr && clk2)
    || ((clk1 && clk2 && clk1->index() < clk2->index())
        || (clk1 == clk2
            && ((pin1 == nullptr && pin2)
                || (pin1 && pin2 && network_->id(pin1) < network_->id(pin2)))));
}

////////////////////////////////////////////////////////////////

ClockInsertionkLess::ClockInsertionkLess(const Network *network) :
  network_(network)
{
}

bool
ClockInsertionkLess::operator()(const ClockInsertion *insert1,
                                const ClockInsertion *insert2) const
{
  const Clock *clk1 = insert1->clock();
  const Clock *clk2 = insert2->clock();
  const Pin *pin1 = insert1->pin();
  const Pin *pin2 = insert2->pin();
  return (clk1 == nullptr && clk2)
    || ((clk1 && clk2 && clk1->index() < clk2->index())
        || (clk1 == clk2
            && ((pin1 == nullptr && pin2)
                || (pin1 && pin2 && network_->id(pin1) < network_->id(pin2)))));
}

////////////////////////////////////////////////////////////////

ClockGroups *
Sdc::makeClockGroups(std::string_view name,
                     bool logically_exclusive,
                     bool physically_exclusive,
                     bool asynchronous,
                     bool allow_paths,
                     std::string_view comment)
{
  std::string group_name;
  if (name.empty())
    group_name = makeClockGroupsName();
  else {
    group_name = name;
    ClockGroups *groups = findStringKey(clk_groups_name_map_, group_name);
    if (groups)
      removeClockGroups(groups);
  }
  ClockGroups *groups = new ClockGroups(group_name, logically_exclusive,
                                        physically_exclusive,
                                        asynchronous, allow_paths,
                                        comment);
  clk_groups_name_map_[groups->name()] = groups;
  return groups;
}

// Generate a name for the clock group.
std::string
Sdc::makeClockGroupsName()
{
  std::string name;
  int i = 0;
  do {
    i++;
    name = sta::format("group{}", i);
  } while (clk_groups_name_map_.contains(name));
  return name;
}

void
Sdc::makeClockGroup(ClockGroups *clk_groups,
                    ClockSet *clks)
{
  clk_groups->makeClockGroup(clks);
}

void
Sdc::ensureClkGroupExclusions()
{
  if (clk_group_exclusions_.empty()) {
    for (const auto &[name, clk_groups] : clk_groups_name_map_)
      makeClkGroupExclusions(clk_groups);
  }
}
   
void
Sdc::makeClkGroupExclusions(ClockGroups *clk_groups)
{
  if (!(clk_groups->asynchronous()
        && clk_groups->allowPaths())) {
    ClockGroupSet *groups = clk_groups->groups();
    if (groups->size() == 1)
      makeClkGroupExclusions1(groups);
    else
      makeClkGroupExclusions(groups);
  }
}

// If there is only one group all clocks not in the group
// are excluded.
void
Sdc::makeClkGroupExclusions1(ClockGroupSet *groups)
{
  ClockGroup *group1 = *groups->begin();
  for (Clock *clk1 : *group1) {
    for (Clock *clk2 : clocks_) {
      if (clk2 != clk1
          && !group1->contains(clk2))
        clk_group_exclusions_.insert(ClockPair(clk1, clk2));
    }
  }
  makeClkGroupSame(group1);
}

void
Sdc::makeClkGroupExclusions(ClockGroupSet *groups)
{
  for (auto group1 : *groups) {
    for (auto group2 : *groups) {
      if (group1 != group2) {
        for (auto clk1 : *group1) {
          for (auto clk2 : *group2) {
            // ClockPair is symmetric so only add one clk1/clk2 pair.
            if (clk1->index() < clk2->index()) {
              clk_group_exclusions_.insert(ClockPair(clk1, clk2));
            }
          }
        }
      }
    }
    makeClkGroupSame(group1);
  }
}

void
Sdc::makeClkGroupSame(ClockGroup *group)
{
  for (auto clk1 : *group) {
    for (auto clk2 : *group) {
      if (clk1->index() <= clk2->index()) {
        ClockPair clk_pair(clk1, clk2);
        if (!clk_group_same_.contains(clk_pair))
          clk_group_same_.insert(clk_pair);
      }
    }
  }
}

void
Sdc::clearClkGroupExclusions()
{
  clk_group_exclusions_.clear();
  clk_group_same_.clear();
}

bool
Sdc::sameClockGroup(const Clock *clk1,
                    const Clock *clk2) const
{
  if (clk1 && clk2) {
    ClockPair clk_pair(clk1, clk2);
    bool excluded = clk_group_exclusions_.contains(clk_pair);
    return !excluded;
  }
  else
    return true;
}

bool
Sdc::sameClockGroupExplicit(const Clock *clk1,
                            const Clock *clk2)
{
  ClockPair clk_pair(clk1, clk2);
  return clk_group_same_.contains(clk_pair);
}

void
Sdc::removeClockGroups(const std::string &name)
{
  ClockGroups *clk_groups = findKey(clk_groups_name_map_, name);
  if (clk_groups)
    removeClockGroups(clk_groups);
}

void
Sdc::removeClockGroupsLogicallyExclusive()
{

  for (const auto &[name, groups] : clk_groups_name_map_) {
    if (groups->logicallyExclusive())
      removeClockGroups(groups);
  }
}

void
Sdc::removeClockGroupsLogicallyExclusive(const std::string &name)
{
  ClockGroups *groups = findKey(clk_groups_name_map_, name);
  if (groups && groups->logicallyExclusive())
    removeClockGroups(groups);
}

void
Sdc::removeClockGroupsPhysicallyExclusive()
{
  for (const auto &[name, groups] : clk_groups_name_map_) {
    if (groups->physicallyExclusive())
      removeClockGroups(groups);
  }
}

void
Sdc::removeClockGroupsPhysicallyExclusive(const std::string &name)
{
  ClockGroups *groups = findKey(clk_groups_name_map_, name);
  if (groups && groups->physicallyExclusive())
    removeClockGroups(groups);
}

void
Sdc::removeClockGroupsAsynchronous()
{
  for (const auto &[name, groups] : clk_groups_name_map_) {
    if (groups->asynchronous())
      removeClockGroups(groups);
  }
}

void
Sdc::removeClockGroupsAsynchronous(const std::string &name)
{
  ClockGroups *groups = findKey(clk_groups_name_map_, name);
  if (groups && groups->asynchronous())
    removeClockGroups(groups);
}

void
Sdc::removeClockGroups(ClockGroups *groups)
{
  clk_groups_name_map_.erase(groups->name());
  delete groups;
  // Can't delete excluded clock pairs for deleted clock groups because
  // some other clock groups may exclude the same clock pair.
  clearClkGroupExclusions();
}

void
Sdc::clockGroupsDeleteClkRefs(Clock *clk)
{
  for (const auto &[name, groups] : clk_groups_name_map_)
    groups->removeClock(clk);
  clearClkGroupExclusions();
}

////////////////////////////////////////////////////////////////

void
Sdc::setClockSense(PinSet *pins,
                   ClockSet *clks,
                   ClockSense sense)
{
  if (clks && clks->empty()) {
    delete clks;
    clks = nullptr;
  }
  for (const Pin *pin : *pins) {
    if (clks) {
      for (const Clock *clk : *clks)
        setClockSense(pin, clk, sense);
    }
    else
      setClockSense(pin, nullptr, sense);
  }
  delete pins;
  delete clks;
}

void
Sdc::setClockSense(const Pin *pin,
                   const Clock *clk,
                   ClockSense sense)
{
  PinClockPair probe(pin, clk);
  if (clk_sense_map_.contains(probe))
    clk_sense_map_[probe] = sense;
  else {
    PinClockPair pin_clk(pin, clk);
    clk_sense_map_[pin_clk] = sense;
  }
}

bool
Sdc::clkStopPropagation(const Pin *pin,
                        const Clock *clk) const
{
  PinClockPair pin_clk(pin, clk);
  ClockSense sense;
  bool exists;
  findKeyValue(clk_sense_map_, pin_clk, sense, exists);
  if (!exists) {
    PinClockPair pin_clk1(pin, nullptr);
    findKeyValue(clk_sense_map_, pin_clk1, sense, exists);
  }
  return exists
    && sense == ClockSense::stop;
}

bool
Sdc::clkStopSense(const Pin *to_pin,
                  const Clock *clk,
                  const RiseFall *from_rf,
                  const RiseFall *to_rf) const
{
  PinClockPair pin_clk(to_pin, clk);
  ClockSense sense;
  bool exists;
  findKeyValue(clk_sense_map_, pin_clk, sense, exists);
  if (!exists) {
    PinClockPair pin(to_pin, nullptr);
    findKeyValue(clk_sense_map_, pin, sense, exists);
  }
  return exists
    && (sense == ClockSense::stop
        || (sense == ClockSense::positive
            && from_rf != to_rf)
        || (sense == ClockSense::negative
            && from_rf == to_rf));
}

bool
Sdc::clkStopPropagation(const Clock *clk,
                        const Pin *from_pin,
                        const RiseFall *from_rf,
                        const Pin *to_pin,
                        const RiseFall *to_rf) const
{
  return clkStopPropagation(from_pin, clk)
    || clkStopSense(to_pin, clk, from_rf, to_rf);
}

PinClockPairLess::PinClockPairLess(const Network *network) :
  network_(network)
{
}

bool
PinClockPairLess::operator()(const PinClockPair &pin_clk1,
                             const PinClockPair &pin_clk2) const
{
  const auto& [pin1, clk1] = pin_clk1;
  const auto& [pin2, clk2] = pin_clk2;
  return pin1 < pin2
    || (pin1 == pin2
        && ((clk1 == nullptr && clk2)
            || (clk1 && clk2
                && clk1->index() < clk2->index())));
}

////////////////////////////////////////////////////////////////

void
Sdc::setClockGatingCheck(const RiseFallBoth *rf,
                         const SetupHold *setup_hold,
                         float margin)
{
  if (clk_gating_check_ == nullptr)
    clk_gating_check_ = new ClockGatingCheck;
  clk_gating_check_->margins()->setValue(rf, setup_hold, margin);
}

void
Sdc::setClockGatingCheck(Clock *clk,
                         const RiseFallBoth *rf,
                         const SetupHold *setup_hold,
                         float margin)
{
  ClockGatingCheck *check = findKey(clk_gating_check_map_, clk);
  if (check == nullptr) {
    check = new ClockGatingCheck();
    clk_gating_check_map_[clk] = check;
  }
  check->margins()->setValue(rf, setup_hold, margin);
}

void
Sdc::setClockGatingCheck(Instance *inst,
                         const RiseFallBoth *rf,
                         const SetupHold *setup_hold,
                         float margin,
                         LogicValue active_value)
{
  ClockGatingCheck *check = findKey(inst_clk_gating_check_map_, inst);
  if (check == nullptr) {
    check = new ClockGatingCheck();
    inst_clk_gating_check_map_[inst] = check;
  }
  check->margins()->setValue(rf, setup_hold, margin);
  check->setActiveValue(active_value);
}

void
Sdc::setClockGatingCheck(const Pin *pin,
                         const RiseFallBoth *rf,
                         const SetupHold *setup_hold,
                         float margin,
                         LogicValue active_value)
{
  ClockGatingCheck *check = findKey(pin_clk_gating_check_map_, pin);
  if (check == nullptr) {
    check = new ClockGatingCheck();
    pin_clk_gating_check_map_[pin] = check;
  }
  check->margins()->setValue(rf, setup_hold, margin);
  check->setActiveValue(active_value);
}

void
Sdc::clockGatingMarginEnablePin(const Pin *enable_pin,
                                const RiseFall *enable_rf,
                                const SetupHold *setup_hold,
                                bool &exists, float &margin) const
{
  ClockGatingCheck *check = findKey(pin_clk_gating_check_map_, enable_pin);
  if (check)
    check->margins()->value(enable_rf, setup_hold, margin, exists);
  else
    exists = false;
}

void
Sdc::clockGatingMarginInstance(Instance *inst,
                               const RiseFall *enable_rf,
                               const SetupHold *setup_hold,
                               bool &exists,
                               float &margin) const
{
  ClockGatingCheck *check = findKey(inst_clk_gating_check_map_, inst);
  if (check)
    check->margins()->value(enable_rf, setup_hold, margin, exists);
  else
    exists = false;
}

void
Sdc::clockGatingMarginClkPin(const Pin *clk_pin,
                             const RiseFall *enable_rf,
                             const SetupHold *setup_hold,
                             bool &exists,
                             float &margin) const
{
  ClockGatingCheck *check = findKey(pin_clk_gating_check_map_, clk_pin);
  if (check)
    check->margins()->value(enable_rf, setup_hold, margin, exists);
  else
    exists = false;
}

void
Sdc::clockGatingMarginClk(const Clock *clk,
                          const RiseFall *enable_rf,
                          const SetupHold *setup_hold,
                          bool &exists,
                          float &margin) const
{
  ClockGatingCheck *check = findKey(clk_gating_check_map_, clk);
  if (check)
    check->margins()->value(enable_rf, setup_hold, margin, exists);
  else
    exists = false;
}

void
Sdc::clockGatingMargin(const RiseFall *enable_rf,
                       const SetupHold *setup_hold,
                       bool &exists,
                       float &margin) const
{
  if (clk_gating_check_)
    clk_gating_check_->margins()->value(enable_rf, setup_hold, margin, exists);
  else
    exists = false;
}

LogicValue
Sdc::clockGatingActiveValue(const Pin *clk_pin,
                            const Pin *enable_pin) const
{
  ClockGatingCheck *check;
  check = findKey(pin_clk_gating_check_map_, enable_pin);
  if (check)
    return check->activeValue();
  Instance *inst = network_->instance(enable_pin);
  check = findKey(inst_clk_gating_check_map_, inst);
  if (check)
    return check->activeValue();
  check = findKey(pin_clk_gating_check_map_, clk_pin);
  if (check)
    return check->activeValue();
  return LogicValue::unknown;
}

////////////////////////////////////////////////////////////////

// Determine cycle accounting "on demand".
CycleAccting *
Sdc::cycleAccting(const ClockEdge *src,
                  const ClockEdge *tgt)
{
  LockGuard lock(cycle_acctings_lock_);
  return cycle_acctings_.cycleAccting(src, tgt);
}

void
Sdc::reportClkToClkMaxCycleWarnings()
{
  cycle_acctings_.reportClkToClkMaxCycleWarnings(report_);
}

void
Sdc::clearCycleAcctings()
{
  cycle_acctings_.clear();
}

////////////////////////////////////////////////////////////////

void
Sdc::setDataCheck(Pin *from,
                  const RiseFallBoth *from_rf,
                  Pin *to,
                  const RiseFallBoth *to_rf,
                  Clock *clk,
                  const SetupHoldAll *setup_hold,
                  float margin)
{
  DataCheck *check = nullptr;
  DataCheckSet *checks = findKey(data_checks_from_map_, from);
  if (checks == nullptr) {
    checks = new DataCheckSet(DataCheckLess(network_));
    data_checks_from_map_[from] = checks;
  }
  else {
    DataCheck probe(from, to, clk);
    check = findKey(*checks, &probe);
  }
  if (check == nullptr)
    check = new DataCheck(from, to, clk);
  check->setMargin(from_rf, to_rf, setup_hold, margin);
  checks->insert(check);

  checks = findKey(data_checks_to_map_, to);
  if (checks == nullptr) {
    checks = new DataCheckSet(DataCheckLess(network_));
    data_checks_to_map_[to] = checks;
  }
  checks->insert(check);
}

void
Sdc::removeDataCheck(Pin *from,
                     const RiseFallBoth *from_rf,
                     Pin *to,
                     const RiseFallBoth *to_rf,
                     Clock *clk,
                     const SetupHoldAll *setup_hold)
{
  DataCheck probe(from, to, clk);
  DataCheckSet *checks = findKey(data_checks_from_map_, from);
  if (checks) {
    DataCheck *check = findKey(*checks, &probe);
    if (check) {
      check->removeMargin(from_rf, to_rf, setup_hold);
      if (check->empty()) {
        checks->erase(check);
        checks = findKey(data_checks_to_map_, to);
        if (checks)
          checks->erase(check);
        delete check;
      }
    }
  }
}

DataCheckSet *
Sdc::dataChecksFrom(const Pin *from) const
{
  return findKey(data_checks_from_map_, from);
}

DataCheckSet *
Sdc::dataChecksTo(const Pin *to) const
{
  return findKey(data_checks_to_map_, to);
}

////////////////////////////////////////////////////////////////

void
Sdc::setLatchBorrowLimit(const Pin *pin,
                         float limit)
{
  pin_latch_borrow_limit_map_[pin] = limit;
}

void
Sdc::setLatchBorrowLimit(const Instance *inst,
                         float limit)
{
  inst_latch_borrow_limit_map_[inst] = limit;
}

void
Sdc::setLatchBorrowLimit(const Clock *clk,
                         float limit)
{
  clk_latch_borrow_limit_map_[clk] = limit;
}

void
Sdc::deleteLatchBorrowLimitsReferencing(Clock *clk)
{
  clk_latch_borrow_limit_map_.erase(clk);
}

void
Sdc::latchBorrowLimit(const Pin *data_pin,
                      const Pin *enable_pin,
                      const Clock *clk,
                      // Return values.
                      float &limit,
                      bool &exists)
{
  findKeyValue(pin_latch_borrow_limit_map_, data_pin, limit, exists);
  if (!exists) {
    findKeyValue(pin_latch_borrow_limit_map_, enable_pin, limit, exists);
    if (!exists) {
      Instance *inst = network_->instance(data_pin);
      findKeyValue(inst_latch_borrow_limit_map_, inst, limit, exists);
      if (!exists)
        findKeyValue(clk_latch_borrow_limit_map_, clk, limit, exists);
    }
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::setMinPulseWidth(const RiseFallBoth *rf,
                      float min_width)
{
  for (auto rf1 : rf->range())
    min_pulse_width_.setValue(rf1, min_width);
}

void
Sdc::setMinPulseWidth(const Pin *pin,
                      const RiseFallBoth *rf,
                      float min_width)
{
  RiseFallValues *widths = findKey(pin_min_pulse_width_map_, pin);
  if (widths == nullptr) {
    widths = new RiseFallValues;
    pin_min_pulse_width_map_[pin] = widths;
  }
  for (auto rf1 : rf->range())
    widths->setValue(rf1, min_width);
}

void
Sdc::setMinPulseWidth(const Instance *inst,
                      const RiseFallBoth *rf,
                      float min_width)
{
  RiseFallValues *widths = findKey(inst_min_pulse_width_map_, inst);
  if (widths == nullptr) {
    widths = new RiseFallValues;
    inst_min_pulse_width_map_[inst] = widths;
  }
  for (auto rf1 : rf->range())
    widths->setValue(rf1, min_width);
}

void
Sdc::setMinPulseWidth(const Clock *clk,
                      const RiseFallBoth *rf,
                      float min_width)
{
  RiseFallValues *widths = findKey(clk_min_pulse_width_map_, clk);
  if (widths == nullptr) {
    widths = new RiseFallValues;
    clk_min_pulse_width_map_[clk] = widths;
  }
  for (auto rf1 : rf->range())
    widths->setValue(rf1, min_width);
}

void
Sdc::minPulseWidth(const Pin *pin,
                   const Clock *clk,
                   const RiseFall *hi_low,
                   float &min_width,
                   bool &exists) const
{
  RiseFallValues *widths = findKey(pin_min_pulse_width_map_, pin);
  if (widths)
    widths->value(hi_low, min_width, exists);
  else {
    if (pin) {
      const Instance *inst = network_->instance(pin);
      widths = findKey(inst_min_pulse_width_map_, inst);
    }
    if (widths == nullptr)
      widths = findKey(clk_min_pulse_width_map_, clk);
    if (widths)
      widths->value(hi_low, min_width, exists);
    else
      min_pulse_width_.value(hi_low, min_width, exists);
  }
}

void
Sdc::deleteMinPulseWidthReferencing(Clock *clk)
{
  RiseFallValues *widths = findKey(clk_min_pulse_width_map_, clk);
  if (widths) {
    delete widths;
    clk_min_pulse_width_map_.erase(clk);
  }
}

////////////////////////////////////////////////////////////////

InputDrive *
Sdc::findInputDrive(Port *port) const
{
  return findKey(input_drive_map_, port);
}

void
Sdc::setInputDelay(const Pin *pin,
                   const RiseFallBoth *rf,
                   const Clock *clk,
                   const RiseFall *clk_rf,
                   const Pin *ref_pin,
                   bool source_latency_included,
                   bool network_latency_included,
                   const MinMaxAll *min_max,
                   bool add,
                   float delay)
{
  ClockEdge *clk_edge = clk ? clk->edge(clk_rf) : nullptr;
  InputDelay *input_delay = findInputDelay(pin, clk_edge);
  if (input_delay == nullptr)
    input_delay = makeInputDelay(pin, clk_edge);
  if (add) {
    RiseFallMinMax *delays = input_delay->delays();
    delays->mergeValue(rf, min_max, delay);
  }
  else {
    deleteInputDelays(pin, input_delay);
    RiseFallMinMax *delays = input_delay->delays();
    delays->setValue(rf, min_max, delay);
  }

  input_delay->setRefPin(ref_pin);
  if (ref_pin)
    have_input_delay_ref_pins_ = true;
  input_delay->setSourceLatencyIncluded(source_latency_included);
  input_delay->setNetworkLatencyIncluded(network_latency_included);
}

InputDelay *
Sdc::makeInputDelay(const Pin *pin,
                    const ClockEdge *clk_edge)
{
  InputDelay *input_delay = new InputDelay(pin, clk_edge, input_delay_index_++,
                                           network_);
  input_delays_.insert(input_delay);
  InputDelaySet *inputs = findKey(input_delay_pin_map_, pin);
  if (inputs == nullptr) {
    inputs = new InputDelaySet;
    input_delay_pin_map_[pin] = inputs;
  }
  inputs->insert(input_delay);

  for (const Pin *lpin : input_delay->leafPins()) {
    InputDelaySet *leaf_inputs = input_delay_leaf_pin_map_[lpin];
    if (leaf_inputs == nullptr) {
      leaf_inputs = new InputDelaySet;
      input_delay_leaf_pin_map_[lpin] = leaf_inputs;
    }
    leaf_inputs->insert(input_delay);

    if (!network_->isTopLevelPort(lpin)) {
      InputDelaySet *internal_inputs = input_delay_internal_pin_map_[lpin];
      if (internal_inputs == nullptr) {
        internal_inputs = new InputDelaySet;
        input_delay_internal_pin_map_[pin] = internal_inputs;
      }
      internal_inputs->insert(input_delay);
    }
  }
  return input_delay;
}

InputDelay *
Sdc::findInputDelay(const Pin *pin,
                    const ClockEdge *clk_edge)
{
  InputDelaySet *inputs = findKey(input_delay_pin_map_, pin);
  if (inputs) {
    for (InputDelay *input_delay : *inputs) {
      if (input_delay->clkEdge() == clk_edge)
        return input_delay;
    }
  }
  return nullptr;
}

void
Sdc::removeInputDelay(const Pin *pin,
                      const RiseFallBoth *rf,
                      const Clock *clk,
                      const RiseFall *clk_rf,
                      const MinMaxAll *min_max)
{
  ClockEdge *clk_edge = clk ? clk->edge(clk_rf) : nullptr;
  InputDelay *input_delay = findInputDelay(pin, clk_edge);
  if (input_delay) {
    RiseFallMinMax *delays = input_delay->delays();
    delays->removeValue(rf, min_max);
    if (delays->empty())
      deleteInputDelay(input_delay);
  }
}

void
Sdc::deleteInputDelays(const Pin *pin,
                       InputDelay *except)
{
  InputDelaySet *input_delays = input_delay_pin_map_[pin];
  for (auto itr = input_delays->begin(); itr != input_delays->end(); /* no incr */) {
    InputDelay *input_delay = *itr;
    if (input_delay != except) {
      itr = input_delays->erase(itr);
      deleteInputDelay(input_delay);
    }
    else
      itr++;
  }
}

InputDelaySet *
Sdc::inputDelaysLeafPin(const Pin *leaf_pin) const
{
  return findKey(input_delay_leaf_pin_map_, leaf_pin);
}

bool
Sdc::hasInputDelay(const Pin *leaf_pin) const
{
  InputDelaySet *input_delays = findKey(input_delay_leaf_pin_map_, leaf_pin);
  return input_delays && !input_delays->empty();
}

bool
Sdc::isInputDelayInternal(const Pin *pin) const
{
  return input_delay_internal_pin_map_.contains(pin);
}

void
Sdc::deleteInputDelaysReferencing(const Clock *clk)
{
  for (auto itr = input_delays_.begin(); itr != input_delays_.end(); ) {
    InputDelay *input_delay = *itr;
    if (input_delay->clock() == clk) {
      itr = input_delays_.erase(itr);
      deleteInputDelay(input_delay);
    }
    else
      itr++;
  }
}

void
Sdc::deleteInputDelay(InputDelay *input_delay)
{
  input_delays_.erase(input_delay);

  const Pin *pin = input_delay->pin();
  InputDelaySet *inputs = input_delay_pin_map_[pin];
  inputs->erase(input_delay);

  for (const Pin *lpin : input_delay->leafPins()) {
    InputDelaySet *inputs = input_delay_leaf_pin_map_[lpin];
    inputs->erase(input_delay);
  }

  delete input_delay;
}

void
Sdc::swapPortDelays(Sdc *sdc1,
                    Sdc *sdc2)
{
  std::swap(sdc1->input_delays_, sdc2->input_delays_);
  std::swap(sdc1->input_delay_pin_map_, sdc2->input_delay_pin_map_);
  std::swap(sdc1->input_delay_leaf_pin_map_, sdc2->input_delay_leaf_pin_map_);
  std::swap(sdc1->input_delay_internal_pin_map_, sdc2->input_delay_internal_pin_map_);
  std::swap(sdc1->input_delay_index_, sdc2->input_delay_index_);

  std::swap(sdc1->output_delays_, sdc2->output_delays_);
  std::swap(sdc1->output_delay_pin_map_, sdc2->output_delay_pin_map_);
  std::swap(sdc1->output_delay_leaf_pin_map_, sdc2->output_delay_leaf_pin_map_);
}

void
Sdc::ensureInputDelayRefPinEdges()
{
  if (have_input_delay_ref_pins_) {
    for (InputDelay *input_delay : input_delays_) {
      const Pin *ref_pin = input_delay->refPin();
      if (ref_pin
          && !input_delay->refPinEdgesExist()) {
        Vertex *ref_pin_vertex = graph_->pinLoadVertex(ref_pin);
        for (const Pin *pin : input_delay->leafPins()) {
          Vertex *input_vertex = graph_->pinDrvrVertex(pin);
          graph_->makeEdge(ref_pin_vertex, input_vertex,
                           TimingArcSet::portRefPinTimingArcSet());
        }
        input_delay->setRefPinEdgesExist(true);
      }
    }
  }
}

// The ref_pin edges are owned by the graph. When the graph is deleted the
// edges go with it, so the existence flags must be reset to force them to be
// rebuilt by ensureInputDelayRefPinEdges() with the new graph.
void
Sdc::inputDelayRefPinEdgesInvalid()
{
  if (have_input_delay_ref_pins_) {
    for (InputDelay *input_delay : input_delays_)
      input_delay->setRefPinEdgesExist(false);
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::setOutputDelay(const Pin *pin,
                    const RiseFallBoth *rf,
                    const Clock *clk,
                    const RiseFall *clk_rf,
                    const Pin *ref_pin,
                    bool source_latency_included,
                    bool network_latency_included,
                    const MinMaxAll *min_max,
                    bool add,
                    float delay)
{
  ClockEdge *clk_edge = clk ? clk->edge(clk_rf) : nullptr;
  OutputDelay *output_delay = findOutputDelay(pin, clk_edge);
  if (output_delay == nullptr)
    output_delay = makeOutputDelay(pin, clk_edge);
  if (add) {
    RiseFallMinMax *delays = output_delay->delays();
    delays->mergeValue(rf, min_max, delay);
  }
  else {
    deleteOutputDelays(pin, output_delay);
    RiseFallMinMax *delays = output_delay->delays();
    delays->setValue(rf, min_max, delay);
  }

  output_delay->setRefPin(ref_pin);
  output_delay->setSourceLatencyIncluded(source_latency_included);
  output_delay->setNetworkLatencyIncluded(network_latency_included);
}

OutputDelay *
Sdc::findOutputDelay(const Pin *pin,
                     const ClockEdge *clk_edge)
{
  OutputDelaySet *outputs = findKey(output_delay_pin_map_, pin);
  if (outputs) {
    for (OutputDelay *output_delay : *outputs) {
      if (output_delay->clkEdge() == clk_edge)
        return output_delay;
    }
  }
  return nullptr;
}

OutputDelay *
Sdc::makeOutputDelay(const Pin *pin,
                     const ClockEdge *clk_edge)
{
  OutputDelay *output_delay = new OutputDelay(pin, clk_edge, network_);
  output_delays_.insert(output_delay);
  OutputDelaySet *outputs = findKey(output_delay_pin_map_, pin);
  if (outputs == nullptr) {
    outputs = new OutputDelaySet;
    output_delay_pin_map_[pin] = outputs;
  }
  outputs->insert(output_delay);

  for (const Pin *lpin : output_delay->leafPins()) {
    OutputDelaySet *leaf_outputs = output_delay_leaf_pin_map_[lpin];
    if (leaf_outputs == nullptr) {
      leaf_outputs = new OutputDelaySet;
      output_delay_leaf_pin_map_[lpin] = leaf_outputs;
    }
    leaf_outputs->insert(output_delay);
  }
  return output_delay;
}

void
Sdc::removeOutputDelay(const Pin *pin,
                       const RiseFallBoth *rf,
                       const Clock *clk,
                       const RiseFall *clk_rf,
                       const MinMaxAll *min_max)
{
  ClockEdge *clk_edge = clk ? clk->edge(clk_rf) : nullptr;
  OutputDelay *output_delay = findOutputDelay(pin, clk_edge);
  if (output_delay) {
    RiseFallMinMax *delays = output_delay->delays();
    delays->removeValue(rf, min_max);
  }
}

void
Sdc::deleteOutputDelays(const Pin *pin,
                        OutputDelay *except)
{
  OutputDelaySet *output_delays = output_delay_pin_map_[pin];
  for (auto itr = output_delays->begin(); itr != output_delays->end(); ) {
    OutputDelay *output_delay = *itr;
    if (output_delay != except) {
      itr = output_delays->erase(itr);
      deleteOutputDelay(output_delay);
    }
    else
      itr++;
  }
}

OutputDelaySet *
Sdc::outputDelaysLeafPin(const Pin *leaf_pin) const
{
  return findKey(output_delay_leaf_pin_map_, leaf_pin);
}

bool
Sdc::hasOutputDelay(const Pin *leaf_pin) const
{
  return output_delay_leaf_pin_map_.contains(leaf_pin);
}

void
Sdc::deleteOutputDelaysReferencing(const Clock *clk)
{
  for (auto itr = output_delays_.begin(); itr != output_delays_.end(); ) {
    OutputDelay *output_delay = *itr;
    if (output_delay->clock() == clk) {
      itr = output_delays_.erase(itr);
      deleteOutputDelay(output_delay);
    }
    else
      itr++;
  }
}

void
Sdc::deleteOutputDelay(OutputDelay *output_delay)
{
  output_delays_.erase(output_delay);

  const Pin *pin = output_delay->pin();
  OutputDelaySet *outputs = output_delay_pin_map_[pin];
  outputs->erase(output_delay);

  for (const Pin *lpin : output_delay->leafPins()) {
    OutputDelaySet *outputs = output_delay_leaf_pin_map_[lpin];
    outputs->erase(output_delay);
  }

  delete output_delay;
}

////////////////////////////////////////////////////////////////

void
Sdc::setPortExtPinCap(const Port *port,
                      const RiseFall *rf,
                      const MinMax *min_max,
                      float cap)
{
  PortExtCap &port_cap = port_ext_cap_map_[port];
  port_cap.setPinCap(port, cap, rf, min_max);
}

void
Sdc::setPortExtWireCap(const Port *port,
                       const RiseFall *rf,
                       const MinMax *min_max,
                       float cap)
{
  PortExtCap &port_cap = port_ext_cap_map_[port];
  port_cap.setWireCap(port, cap, rf, min_max);
}

const PortExtCap *
Sdc::portExtCap(const Port *port) const
{
  auto itr = port_ext_cap_map_.find(port);
  if (itr != port_ext_cap_map_.end())
    return &itr->second;
  else
    return nullptr;
}

bool
Sdc::hasPortExtCap(const Port *port) const
{
  auto itr = port_ext_cap_map_.find(port);
  return itr != port_ext_cap_map_.end();
}

void
Sdc::portExtCap(const Port *port,
                const RiseFall *rf,
                const MinMax *min_max,
                // Return values.
                float &pin_cap,
                bool &has_pin_cap,
                float &wire_cap,
                bool &has_wire_cap,
                int &fanout,
                bool &has_fanout) const
{
  const PortExtCap *port_cap = portExtCap(port);
  if (port_cap) {
    port_cap->pinCap(rf, min_max, pin_cap, has_pin_cap);
    port_cap->wireCap(rf, min_max, wire_cap, has_wire_cap);
    port_cap->fanout(min_max, fanout, has_fanout);
  }
  else {
    pin_cap = 0.0F;
    has_pin_cap = false;
    wire_cap = 0.0F;
    has_wire_cap = false;
    fanout = 0.0F;
    has_fanout = false;
  }
}

float
Sdc::portExtCap(const Port *port,
                const RiseFall *rf,
                const MinMax *min_max) const
{
  float pin_cap, wire_cap;
  int fanout;
  bool has_pin_cap, has_wire_cap, has_fanout;
  portExtCap(port, rf, min_max,
             pin_cap, has_pin_cap,
             wire_cap, has_wire_cap,
             fanout, has_fanout);
  float cap = 0.0;
  if (has_pin_cap)
    cap += pin_cap;
  if (has_wire_cap)
    cap += wire_cap;
  return cap;
}

bool
Sdc::drvrPinHasWireCap(const Pin *pin) const
{
  return drvr_pin_wire_cap_map_.contains(pin);
}

void
Sdc::drvrPinWireCap(const Pin *pin,
                    const MinMax *min_max,
                    // Return values.
                    float &cap,
                    bool &exists,
                    bool &subtract_pin_cap) const
{
  NetWireCaps *net_caps = findKey(drvr_pin_wire_cap_map_, pin);
  if (net_caps) {
    net_caps->value(min_max, cap, exists);
    subtract_pin_cap = net_caps->subtractPinCap(min_max);
  }
  else {
    cap = 0.0;
    exists = false;
    subtract_pin_cap = false;
  }
}

void
Sdc::setNetWireCap(const Net *net,
                   bool subtract_pin_cap,
                   const MinMax *min_max,
                   float wire_cap)
{
  NetWireCaps &net_caps = net_wire_cap_map_[net];
  net_caps.setValue(min_max, wire_cap);
  net_caps.setSubtractPinCap(subtract_pin_cap, min_max);


  for (const Pin *pin : *network_->drivers(net))
    drvr_pin_wire_cap_map_[pin] = &net_caps;
}

bool
Sdc::hasNetWireCap(const Net *net) const
{
  return net_wire_cap_map_.contains(net);
}

////////////////////////////////////////////////////////////////

void
Sdc::connectedCap(const Pin *pin,
                  const RiseFall *rf,
                  const Scene *scene,
                  const MinMax *min_max,
                  // Return values.
                  float &pin_cap,
                  float &wire_cap,
                  float &fanout,
                  bool &has_net_load) const
{
  netCaps(pin, rf, scene, min_max, pin_cap, wire_cap, fanout, has_net_load);
  float net_wire_cap;
  bool subtract_pin_cap;
  drvrPinWireCap(pin, min_max, net_wire_cap, has_net_load, subtract_pin_cap);
  if (subtract_pin_cap)
    pin_cap = 0.0;
  if (has_net_load)
    wire_cap += net_wire_cap;
}

float
Sdc::connectedPinCap(const Pin *pin,
                     const RiseFall *rf,
                     const Scene *scene,
                     const MinMax *min_max)
{
  float pin_cap, wire_cap, fanout;
  bool has_net_load;
  connectedCap(pin, rf, scene, min_max,
               pin_cap, wire_cap, fanout, has_net_load);
  return pin_cap;
}

class FindNetCaps : public PinVisitor
{
public:
  FindNetCaps(const RiseFall *rf,
              const Scene *scene,
              const MinMax *min_max,
              float &pin_cap,
              float &wire_cap,
              float &fanout,
              bool &has_net_load,
              const Sdc *sdc);
  void operator()(const Pin *pin) override;

protected:
  const RiseFall *rf_;
  const Scene *scene_;
  const MinMax *min_max_;
  float &pin_cap_;
  float &wire_cap_;
  float &fanout_;
  bool &has_net_load_;
  const Sdc *sdc_;
};

FindNetCaps::FindNetCaps(const RiseFall *rf,
                         const Scene *scene,
                         const MinMax *min_max,
                         float &pin_cap,
                         float &wire_cap,
                         float &fanout,
                         bool &has_net_load,
                         const Sdc *sdc) :
  PinVisitor(),
  rf_(rf),
  scene_(scene),
  min_max_(min_max),
  pin_cap_(pin_cap),
  wire_cap_(wire_cap),
  fanout_(fanout),
  has_net_load_(has_net_load),
  sdc_(sdc)
{
}

void
FindNetCaps::operator()(const Pin *pin)
{
  sdc_->pinCaps(pin, rf_, scene_, min_max_,
                pin_cap_, wire_cap_, fanout_);
}

// Capacitances for all pins connected to drvr_pin's net.
void
Sdc::netCaps(const Pin *drvr_pin,
             const RiseFall *rf,
             const Scene *scene,
             const MinMax *min_max,
             // Return values.
             float &pin_cap,
             float &wire_cap,
             float &fanout,
             bool &has_net_load) const
{
  pin_cap = 0.0;
  wire_cap = 0.0;
  fanout = 0.0;
  has_net_load = false;
  FindNetCaps visitor(rf, scene, min_max, pin_cap,
                      wire_cap, fanout, has_net_load, this);
  network_->visitConnectedPins(drvr_pin, visitor);
}

void
Sdc::pinCaps(const Pin *pin,
             const RiseFall *rf,
             const Scene *scene,
             const MinMax *min_max,
             // Return values.
             float &pin_cap,
             float &wire_cap,
             float &fanout) const
{
  if (network_->isTopLevelPort(pin)) {
    Port *port = network_->port(pin);
    bool is_output = network_->direction(port)->isAnyOutput();
    float port_pin_cap, port_wire_cap;
    int port_fanout;
    bool has_pin_cap, has_wire_cap, has_fanout;
    portExtCap(port, rf, min_max,
               port_pin_cap, has_pin_cap,
               port_wire_cap, has_wire_cap,
               port_fanout, has_fanout);
    if (has_pin_cap)
      pin_cap += port_pin_cap;
    if (has_wire_cap)
      wire_cap += port_wire_cap;
    if (is_output) {
      if (has_fanout)
        fanout += port_fanout;
      // Output port counts as a fanout.
      fanout++;
    }
  }
  else {
    LibertyPort *port = network_->libertyPort(pin);
    if (port) {
      Instance *inst = network_->instance(pin);
      pin_cap += portCapacitance(inst, port, rf, scene, min_max);
      if (port->direction()->isAnyInput())
        fanout++;
    }
  }
}

float
Sdc::portCapacitance(Instance *inst,
                     LibertyPort *port,
                     const RiseFall *rf,
                     const Scene *scene,
                     const MinMax *min_max) const
{
  const Pvt *inst_pvt = nullptr;
  if (inst)
    inst_pvt = pvt(inst, min_max);
  LibertyPort *scene_port = port->scenePort(scene, min_max);
  if (scene_port == nullptr)
    scene_port = port;
  OperatingConditions *op_cond = operatingConditions(min_max);
  return scene_port->capacitance(rf, min_max, op_cond, inst_pvt);
}

float
Sdc::pinCapacitance(const Pin *pin,
                    const RiseFall *rf,
                    const Scene *scene,
                    const MinMax *min_max) const
{
  LibertyPort *port = network_->libertyPort(pin);
  if (port) {
    Instance *inst = network_->instance(pin);
    return portCapacitance(inst, port, rf, scene, min_max);
  }
  else
    return 0.0;
}

////////////////////////////////////////////////////////////////

void
Sdc::setResistance(const Net *net,
                   const MinMaxAll *min_max,
                   float res)
{
  MinMaxFloatValues &values = net_res_map_[net];
  values.setValue(min_max, res);
}

void
Sdc::resistance(const Net *net,
                const MinMax *min_max,
                float &res,
                bool &exists) const
{
  res = 0.0;
  MinMaxFloatValues values;
  findKeyValue(net_res_map_, net, values, exists);
  if (exists)
    values.value(min_max, res, exists);
}

void
Sdc::setPortExtFanout(const Port *port,
                      const MinMax *min_max,
                      int fanout)
{
  PortExtCap &port_cap = port_ext_cap_map_[port];
  port_cap.setFanout(port, fanout, min_max);
}

void
Sdc::portExtFanout(const Port *port,
                   const MinMax *min_max,
                   // Return values.
                   int &fanout,
                   bool &exists) const
{
  const PortExtCap *port_cap = portExtCap(port);
  if (port_cap)
    port_cap->fanout(min_max, fanout, exists);
  else {
    fanout = 0.0;
    exists = false;
  }
}

int
Sdc::portExtFanout(Port *port,
                   const MinMax *min_max) const
{
  int fanout;
  bool exists;
  portExtFanout(port, min_max, fanout, exists);
  if (exists)
    return fanout;
  else
    return 0.0;
}

void
Sdc::swapPortExtCaps(Sdc *sdc1,
                     Sdc *sdc2)
{
  std::swap(sdc1->port_ext_cap_map_, sdc2->port_ext_cap_map_);
  std::swap(sdc1->net_wire_cap_map_, sdc2->net_wire_cap_map_);
}

////////////////////////////////////////////////////////////////

void
Sdc::disable(LibertyCell *cell,
             LibertyPort *from,
             LibertyPort *to)
{
  DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
  if (disabled_cell == nullptr) {
    disabled_cell = new DisabledCellPorts(cell);
    disabled_cell_ports_[cell] = disabled_cell;
  }
  if (from && to)
    disabled_cell->setDisabledFromTo(from, to);
  else if (from)
    disabled_cell->setDisabledFrom(from);
  else if (to)
    disabled_cell->setDisabledTo(to);
  else
    disabled_cell->setDisabledAll();
}

void
Sdc::removeDisable(LibertyCell *cell,
                   LibertyPort *from,
                   LibertyPort *to)
{
  DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
  if (disabled_cell) {
    if (from && to)
      disabled_cell->removeDisabledFromTo(from, to);
    else if (from)
      disabled_cell->removeDisabledFrom(from);
    else if (to)
      disabled_cell->removeDisabledTo(to);
    else
      disabled_cell->removeDisabledAll();
  }
}

void
Sdc::disable(TimingArcSet *arc_set)
{
  LibertyCell *cell = arc_set->libertyCell();
  DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
  if (disabled_cell == nullptr) {
    disabled_cell = new DisabledCellPorts(cell);
    disabled_cell_ports_[cell] = disabled_cell;
  }
  disabled_cell->setDisabled(arc_set);
}

void
Sdc::removeDisable(TimingArcSet *arc_set)
{
  LibertyCell *cell = arc_set->libertyCell();
  DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
  if (disabled_cell)
    disabled_cell->removeDisabled(arc_set);
}

void
Sdc::disable(LibertyPort *port)
{
  disabled_lib_ports_.insert(port);
}

void
Sdc::removeDisable(LibertyPort *port)
{
  disabled_lib_ports_.erase(port);
}

void
Sdc::disable(Port *port)
{
  disabled_ports_.insert(port);
}

void
Sdc::removeDisable(Port *port)
{
  disabled_ports_.erase(port);
}

void
Sdc::disable(Instance *inst,
             LibertyPort *from,
             LibertyPort *to)
{
  DisabledInstancePorts *disabled_inst = findKey(disabled_inst_ports_, inst);
  if (disabled_inst == nullptr) {
    disabled_inst = new DisabledInstancePorts(inst);
    disabled_inst_ports_[inst] = disabled_inst;
  }
  if (from && to)
    disabled_inst->setDisabledFromTo(from, to);
  else if (from)
    disabled_inst->setDisabledFrom(from);
  else if (to)
    disabled_inst->setDisabledTo(to);
  else
    disabled_inst->setDisabledAll();
}

void
Sdc::removeDisable(Instance *inst,
                   LibertyPort *from,
                   LibertyPort *to)
{
  DisabledInstancePorts *disabled_inst = findKey(disabled_inst_ports_, inst);
  if (disabled_inst) {
    if (from && to)
      disabled_inst->removeDisabledFromTo(from, to);
    else if (from)
      disabled_inst->removeDisabledFrom(from);
    else if (to)
      disabled_inst->removeDisabledTo(to);
    else
      disabled_inst->removeDisabledAll();
  }
}

void
Sdc::disableWire(const Pin *from,
                 const Pin *to)
{
  disabled_wire_edges_.insert({from, to});
}

void
Sdc::removeDisableWire(Pin *from,
                       Pin *to)
{
  disabled_wire_edges_.erase({from, to});
}

bool
Sdc::isDisabledWire(const Pin *from,
                    const Pin *to) const
{
  return disabled_wire_edges_.contains({from, to});
}

void
Sdc::disable(Edge *edge)
{
  disabled_edges_.insert(edge);
}

void
Sdc::removeDisable(Edge *edge)
{
  disabled_edges_.erase(edge);
}

bool
Sdc::isDisabled(const Edge *edge) const
{
  return disabled_edges_.contains(const_cast<Edge*>(edge));
}

bool
Sdc::isDisabledConstraint(const Edge *edge) const
{
  Pin *from_pin = edge->from(graph_)->pin();
  Pin *to_pin = edge->to(graph_)->pin();
  const Instance *inst = network_->instance(from_pin);
  TimingArcSet *arc_set = edge->timingArcSet();
  return isDisabled(inst, from_pin, to_pin, edge->role())
    || isDisabled(edge)
    || isDisabledWire(from_pin, to_pin)
    || isDisabled(arc_set);
}

class DisableEdgesThruHierPin : public HierPinThruVisitor
{
public:
  DisableEdgesThruHierPin(PinPairSet *pairs,
                          Graph *graph);
  void visit(const Pin *drvr,
             const Pin *load) override;

protected:
  PinPairSet *pairs_;
  Graph *graph_;
};

DisableEdgesThruHierPin::DisableEdgesThruHierPin(PinPairSet *pairs,
                                                 Graph *graph) :
  pairs_(pairs),
  graph_(graph)
{
}

void
DisableEdgesThruHierPin::visit(const Pin *drvr,
                               const Pin *load)
{
  pairs_->insert({drvr, load});
}

void
Sdc::disable(const Pin *pin)
{
  if (network_->isHierarchical(pin)) {
    // Add leaf pins thru hierarchical pin to disabled_edges_.
    DisableEdgesThruHierPin visitor(&disabled_wire_edges_, graph_);
    visitDrvrLoadsThruHierPin(pin, network_, &visitor);
  }
  else
    disabled_pins_.insert(pin);
}

class RemoveDisableEdgesThruHierPin : public HierPinThruVisitor
{
public:
  RemoveDisableEdgesThruHierPin(PinPairSet *pairs,
                                Graph *graph);
  void visit(const Pin *drvr,
             const Pin *load) override;

protected:
  PinPairSet *pairs_;
  Graph *graph_;
};

RemoveDisableEdgesThruHierPin::RemoveDisableEdgesThruHierPin(PinPairSet *pairs,
                                                             Graph *graph) :
  pairs_(pairs),
  graph_(graph)
{
}

void
RemoveDisableEdgesThruHierPin::visit(const Pin *drvr,
                                     const Pin *load)
{
  pairs_->erase({drvr, load});
}

void
Sdc::removeDisable(Pin *pin)
{
  if (network_->isHierarchical(pin)) {
    // Remove leaf pins thru hierarchical pin from disabled_edges_.
    RemoveDisableEdgesThruHierPin visitor(&disabled_wire_edges_, graph_);
    visitDrvrLoadsThruHierPin(pin, network_, &visitor);
  }
  else
    disabled_pins_.erase(pin);
}

bool
Sdc::isDisabledConstraint(const Pin *pin) const
{
  Port *port = network_->port(pin);
  LibertyPort *lib_port = network_->libertyPort(pin);
  return disabled_pins_.contains(pin)
    || disabled_ports_.contains(port)
    || disabled_lib_ports_.contains(lib_port);
}

bool
Sdc::isDisabled(const Instance *inst,
                const Pin *from_pin,
                const Pin *to_pin,
                const TimingRole *role) const
{
  if (role == TimingRole::wire()) {
    // Hierarchical thru pin disables.
    return disabled_wire_edges_.contains({from_pin, to_pin});
  }
  else {
    LibertyCell *cell = network_->libertyCell(inst);
    LibertyPort *from_port = network_->libertyPort(from_pin);
    LibertyPort *to_port = network_->libertyPort(to_pin);
    DisabledInstancePorts *disabled_inst = findKey(disabled_inst_ports_, inst);
    DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
    return (disabled_inst
            && disabled_inst->isDisabled(from_port, to_port, role))
      || (disabled_cell
          && disabled_cell->isDisabled(from_port, to_port, role));
  }
}

bool
Sdc::isDisabled(TimingArcSet *arc_set) const
{
  LibertyCell *cell = arc_set->libertyCell();
  if (cell) {
    DisabledCellPorts *disabled_cell = findKey(disabled_cell_ports_, cell);
    return disabled_cell
      && disabled_cell->isDisabled(arc_set);
  }
  else
    return false;
}

const DisabledInstancePortsMap *
Sdc::disabledInstancePorts() const
{
  return &disabled_inst_ports_;
}

const DisabledCellPortsMap *
Sdc::disabledCellPorts() const
{
  return &disabled_cell_ports_;
}

////////////////////////////////////////////////////////////////

bool
Sdc::isConstrainedEnd(const Pin *pin) const
{
  // All output pins are considered constrained because
  // they may be downstream from a set_min/max_delay -from that
  // does not have a set_output_delay.
  return (network_->isTopLevelPort(pin)
          && network_->direction(pin)->isAnyOutput())
    || output_delay_leaf_pin_map_.contains(pin)
    || data_checks_to_map_.contains(pin);
}

void
Sdc::disableClockGatingCheck(Instance *inst)
{
  disabled_clk_gating_checks_inst_.insert(inst);
}

void
Sdc::disableClockGatingCheck(Pin *pin)
{
  disabled_clk_gating_checks_pin_.insert(pin);
}

void
Sdc::removeDisableClockGatingCheck(Instance *inst)
{
  disabled_clk_gating_checks_inst_.erase(inst);
}

void
Sdc::removeDisableClockGatingCheck(Pin *pin)
{
  disabled_clk_gating_checks_pin_.erase(pin);
}

bool
Sdc::isDisableClockGatingCheck(const Instance *inst) const
{
  return disabled_clk_gating_checks_inst_.contains(inst);
}

bool
Sdc::isDisableClockGatingCheck(const Pin *pin) const
{
  return disabled_clk_gating_checks_pin_.contains(pin);
}

////////////////////////////////////////////////////////////////

void
Sdc::setLogicValue(const Pin *pin,
                   LogicValue value)
{
  logic_value_map_[pin] = value;
}

void
Sdc::logicValue(const Pin *pin,
                LogicValue &value,
                bool &exists) const
{
  findKeyValue(logic_value_map_, pin, value, exists);
}

void
Sdc::setCaseAnalysis(const Pin *pin,
                     LogicValue value)
{
  case_value_map_[pin] = value;
}

void
Sdc::removeCaseAnalysis(const Pin *pin)
{
  case_value_map_.erase(pin);
}

void
Sdc::caseLogicValue(const Pin *pin,
                    LogicValue &value,
                    bool &exists) const
{
  findKeyValue(case_value_map_, pin, value, exists);
}

bool
Sdc::hasLogicValue(const Pin *pin) const
{
  return case_value_map_.contains(pin)
    || logic_value_map_.contains(pin);
}

////////////////////////////////////////////////////////////////

ExceptionFrom *
Sdc::makeExceptionFrom(PinSet *from_pins,
                       ClockSet *from_clks,
                       InstanceSet *from_insts,
                       const RiseFallBoth *from_rf) const
{
  if ((from_pins && !from_pins->empty())
      || (from_clks && !from_clks->empty())
      || (from_insts && !from_insts->empty()))
    return new ExceptionFrom(from_pins, from_clks, from_insts, from_rf,
                             true, network_);
  else
    return nullptr;
}

bool
Sdc::isExceptionStartpoint(const Pin *pin) const
{
  Net *net = network_->net(pin);
  const LibertyPort *port = network_->libertyPort(pin);
  return ((network_->isTopLevelPort(pin)
           && network_->direction(pin)->isAnyInput())
          || (port && port->isRegClk())
          || (port && port->isLatchData()))
    // Pins connected to power/ground are invalid.
    && !(net
         && (network_->isPower(net)
             || network_->isGround(net)))
    && !network_->isHierarchical(pin);
}

ExceptionThru *
Sdc::makeExceptionThru(PinSet *pins,
                       NetSet *nets,
                       InstanceSet *insts,
                       const RiseFallBoth *rf) const
{
  if ((pins && !pins->empty())
      || (nets && !nets->empty())
      || (insts && !insts->empty()))
    return new ExceptionThru(pins, nets, insts, rf, true, network_);
  else
    return nullptr;
}

ExceptionTo *
Sdc::makeExceptionTo(PinSet *pins,
                     ClockSet *clks,
                     InstanceSet *insts,
                     const RiseFallBoth *rf,
                     const RiseFallBoth *end_rf) const
{
  if ((pins && !pins->empty())
      || (clks && !clks->empty())
      || (insts && !insts->empty())
      || (rf != RiseFallBoth::riseFall())
      || (end_rf != RiseFallBoth::riseFall()))
    return new ExceptionTo(pins, clks, insts, rf, end_rf, true, network_);
  else
    return nullptr;
}

// Valid endpoints include gated clock enables which are not
// known until clock arrivals are determined.
bool
Sdc::isExceptionEndpoint(const Pin *pin) const
{
  Net *net = network_->net(pin);
  bool has_checks = false;
  const LibertyPort *port = network_->libertyPort(pin);
  if (port) {
    // Look for timing checks to the pin witihout using the graph because
    // it may not exist.
    LibertyCell *cell = port->libertyCell();
    for (TimingArcSet *arc_set : cell->timingArcSetsTo(port)) {
      if (arc_set->role()->isTimingCheck()) {
        has_checks = true;
        break;
      }
    }
  }
  return ((network_->isTopLevelPort(pin)
           && network_->direction(pin)->isAnyOutput())
          || has_checks
          || (port && port->isLatchData()))
    // Pins connected to power/ground are invalid.
    && !(net
         && (network_->isPower(net)
             || network_->isGround(net)))
    && !network_->isHierarchical(pin);
}

////////////////////////////////////////////////////////////////

void
Sdc::makeFalsePath(ExceptionFrom *from,
                   ExceptionThruSeq *thrus,
                   ExceptionTo *to,
                   const MinMaxAll *min_max,
                   std::string_view comment)
{
  checkFromThrusTo(from, thrus, to);
  FalsePath *exception = new FalsePath(from, thrus, to, min_max, true,
                                       comment);
  addException(exception);
}

void
Sdc::makeMulticyclePath(ExceptionFrom *from,
                        ExceptionThruSeq *thrus,
                        ExceptionTo *to,
                        const MinMaxAll *min_max,
                        bool use_end_clk,
                        int path_multiplier,
                        std::string_view comment)
{
  checkFromThrusTo(from, thrus, to);
  MultiCyclePath *exception = new MultiCyclePath(from, thrus, to,
                                                 min_max, use_end_clk,
                                                 path_multiplier, true,
                                                 comment);
  addException(exception);
}

void
Sdc::makePathDelay(ExceptionFrom *from,
                   ExceptionThruSeq *thrus,
                   ExceptionTo *to,
                   const MinMax *min_max,
                   bool ignore_clk_latency,
                   bool break_path,
                   float delay,
                   std::string_view comment)
{
  checkFromThrusTo(from, thrus, to);
  PathDelay *exception = new PathDelay(from, thrus, to, min_max, 
                                       ignore_clk_latency, break_path,
                                       delay, true, comment);
  addException(exception);
}

void
Sdc::recordPathDelayInternalFrom(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  if (from
      && from->hasPins()) {
    for (const Pin *pin : *from->pins()) {
      if (!isExceptionStartpoint(pin)) {
        path_delay_internal_from_.insert(pin);
        if (exception->breakPath())
          path_delay_internal_from_break_.insert(pin);
      }
    }
  }
}

void
Sdc::unrecordPathDelayInternalFrom(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  if (from
      && from->hasPins()
      && !path_delay_internal_from_.empty()) {
    for (const Pin *pin : *from->pins()) {
      if (!isExceptionStartpoint(pin)
          && !pathDelayFrom(pin)) {
        path_delay_internal_from_.erase(pin);
        if (exception->breakPath())
          path_delay_internal_from_break_.erase(pin);
      }
    }
  }
}

bool
Sdc::pathDelayFrom(const Pin *pin)
{
  const ExceptionPathSet *exceptions = findKeyValuePtr(first_from_pin_exceptions_, pin);
  if (exceptions) {
    for (ExceptionPath *exception : *exceptions) {
      if (exception->isPathDelay())
        return true;
    }
  }
  return false;
}

bool
Sdc::isPathDelayInternalFrom(const Pin *pin) const
{
  return path_delay_internal_from_.contains(pin);
}

bool
Sdc::isPathDelayInternalFromBreak(const Pin *pin) const
{
  return path_delay_internal_from_break_.contains(pin);
}

const PinSet &
Sdc::pathDelayInternalFrom() const
{
  return path_delay_internal_from_;
}

void
Sdc::recordPathDelayInternalTo(ExceptionPath *exception)
{
  ExceptionTo *to = exception->to();
  if (to
      && to->hasPins()) {
    for (const Pin *pin : *to->pins()) {
      if (!(hasLibertyCheckTo(pin)
            || network_->isTopLevelPort(pin))) {
        path_delay_internal_to_.insert(pin);
        if (exception->breakPath())
          path_delay_internal_to_break_.insert(pin);
      }
    }
  }
}

void
Sdc::unrecordPathDelayInternalTo(ExceptionPath *exception)
{
  ExceptionTo *to = exception->to();
  if (to
      && to->hasPins()
      && !path_delay_internal_to_.empty()) {
    for (const Pin *pin : *to->pins()) {
      if (!(hasLibertyCheckTo(pin)
            || network_->isTopLevelPort(pin))
          && !pathDelayTo(pin)) {
        path_delay_internal_to_.erase(pin);
        if (exception->breakPath())
          path_delay_internal_to_break_.erase(pin);
      }
    }
  }
}

bool
Sdc::hasLibertyCheckTo(const Pin *pin)
{
  const Instance *inst = network_->instance(pin);
  LibertyCell *cell = network_->libertyCell(inst);
  if (cell) {
    LibertyPort *port = network_->libertyPort(pin);
    if (port) {
      for (TimingArcSet *arc_set : cell->timingArcSetsTo(port)) {
        if (arc_set->role()->isTimingCheckBetween())
          return true;
      }
    }
  }
  return false;
}

bool
Sdc::pathDelayTo(const Pin *pin)
{
  const ExceptionPathSet *exceptions = findKeyValuePtr(first_to_pin_exceptions_, pin);
  if (exceptions) {
    for (ExceptionPath *exception : *exceptions) {
      if (exception->isPathDelay())
        return true;
    }
  }
  return false;
}

bool
Sdc::isPathDelayInternalTo(const Pin *pin) const
{
  return path_delay_internal_to_.contains(pin);
}

bool
Sdc::isPathDelayInternalToBreak(const Pin *pin) const
{
  return path_delay_internal_to_break_.contains(pin);
}

////////////////////////////////////////////////////////////////

void
Sdc::clearGroupPathMap()
{
  // GroupPath exceptions are deleted with other exceptions.
  for (auto &[name, groups] : group_path_map_) {
    deleteContents(*groups);
    delete groups;
  }
  group_path_map_.clear();
}

void
Sdc::makeGroupPath(std::string_view name,
                   bool is_default,
                   ExceptionFrom *from,
                   ExceptionThruSeq *thrus,
                   ExceptionTo *to,
                   std::string_view comment)
{
  checkFromThrusTo(from, thrus, to);
  if (!name.empty() && is_default)
    report_->critical(1490, "group path name and is_default are mutually exclusive.");
  else if (!name.empty()) {
    const std::string name_key(name);
    GroupPath *group_path = new GroupPath(name, is_default, from, thrus, to,
                                          true, comment);
    // Clone the group_path because it may get merged and hence deleted
    // by addException.
    ExceptionFrom *from1 = group_path->from()
      ? group_path->from()->clone(network_):nullptr;
    ExceptionThruSeq *thrus1 = exceptionThrusClone(group_path->thrus(), network_);
    ExceptionTo *to1 = group_path->to() ? group_path->to()->clone(network_) : nullptr;
    ExceptionPath *clone = group_path->clone(from1, thrus1, to1, true);
    addException(clone);
    // A named group path can have multiple exceptions.
    GroupPathSet *groups = findKey(group_path_map_, name_key);
    if (groups == nullptr) {
      groups = new GroupPathSet(network_);
      group_path_map_[name_key] = groups;
    }
    if (groups->contains(group_path))
      // Exact copy of existing group path.
      delete group_path;
    else
      groups->insert(group_path);
  }
  else {
    // is_default
    GroupPath *group_path = new GroupPath(name, is_default, from, thrus, to,
                                          true, comment);
    addException(group_path);
  }
}

bool
Sdc::isGroupPathName(std::string_view group_name) const
{
  return group_path_map_.contains(group_name);
}

////////////////////////////////////////////////////////////////

FilterPath *
Sdc::makeFilterPath(ExceptionFrom *from,
                    ExceptionThruSeq *thrus,
                    ExceptionTo *to)
{
  checkFromThrusTo(from, thrus, to);
  FilterPath *exception = new FilterPath(from, thrus, to, true);
  addException(exception);
  // This is the only type of exception that can be returned.
  // There is only one of them, so it shouldn't merge.
  return exception;
}

void
Sdc::makeFilter(ExceptionFrom *from,
                ExceptionThruSeq *thrus)
{
  filter_ = makeFilterPath(from, thrus, nullptr);
}

void
Sdc::deleteFilter()
{
  if (filter_) {
    deleteException(filter_);
    filter_ = nullptr;
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::makeLoopExceptions()
{
  for (GraphLoop *loop : levelize_->loops())
    makeLoopExceptions(loop);
}

// Make a -thru pin false path from every edge entering the loop
// around the loop and back.
void
Sdc::makeLoopExceptions(GraphLoop *loop)
{
  debugPrint(debug_, "loop", 2, "Loop false path");
  for (Edge *edge : *loop->edges()) {
    Vertex *from_vertex = edge->from(graph_);
    Vertex *to_vertex = edge->to(graph_);
    Pin *from_pin = from_vertex->pin();
    Pin *to_pin = to_vertex->pin();
    // Find edges entering the loop.
    VertexInEdgeIterator in_edge_iter(to_vertex, graph_);
    while (in_edge_iter.hasNext()) {
      Edge *in_edge = in_edge_iter.next();
      if (in_edge != edge) {
        Pin *loop_input_pin = in_edge->from(graph_)->pin();
        makeLoopException(loop_input_pin, to_pin, from_pin);
        // Prevent sub-loops by blocking paths on the main loop also.
        makeLoopException(from_pin, to_pin, loop_input_pin);
      }
    }
  }
}

void
Sdc::makeLoopException(const Pin *loop_input_pin,
                       const Pin *loop_pin,
                       const Pin *loop_prev_pin)
{
  ExceptionThruSeq *thrus = new ExceptionThruSeq;
  makeLoopExceptionThru(loop_input_pin, thrus);
  makeLoopExceptionThru(loop_pin, thrus);
  makeLoopExceptionThru(loop_prev_pin, thrus);
  makeLoopExceptionThru(loop_pin, thrus);
  makeLoopPath(thrus);
}

void
Sdc::makeLoopPath(ExceptionThruSeq *thrus)
{
  FalsePath *exception = new LoopPath(thrus, true);
  addException(exception);
}

void
Sdc::makeLoopExceptionThru(const Pin *pin,
                           ExceptionThruSeq *thrus)
{
  debugPrint(debug_, "levelize", 2, " {}", network_->pathName(pin));
  PinSet *pins = new PinSet(network_);
  pins->insert(pin);
  ExceptionThru *thru = makeExceptionThru(pins, nullptr, nullptr,
                                          RiseFallBoth::riseFall());
  thrus->push_back(thru);
}

void
Sdc::deleteLoopExceptions()
{
  // erase prevents range iteration.
  for (auto itr = exceptions_.begin(); itr != exceptions_.end(); ) {
    ExceptionPath *except = *itr;
    if (except->isLoop()) {
      itr = exceptions_.erase(itr);
      deleteException(except);
    }
    else
      itr++;
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::addException(ExceptionPath *exception)
{
  debugPrint(debug_, "exception_merge", 1, "add exception for {}",
             exception->to_string(network_));

  if (exception->isPathDelay()) {
    recordPathDelayInternalFrom(exception);
    recordPathDelayInternalTo(exception);
    if (exception->to() == nullptr)
      path_delays_without_to_ = true;
  }

  // Check to see if the exception has from/to mixed object types.
  // If so, the priority of the exception is mixed.
  // Split it into separate exceptions that have consistent priority.
  ExceptionFrom *from = exception->from();
  if (from
      && (from->hasPins() || from->hasInstances())
      && from->hasClocks()
      // There is only one filter so there are no competing priorities.
      && !exception->isFilter()) {
    PinSet *pins1 = from->pins() ? new PinSet(*from->pins()) : nullptr;
    InstanceSet *insts1 =
      from->instances() ? new InstanceSet(*from->instances()) : nullptr;
    ExceptionFrom *from1 = new ExceptionFrom(pins1, nullptr, insts1,
                                             from->transition(), true, network_);
    ExceptionThruSeq *thrus1 = exceptionThrusClone(exception->thrus(), network_);
    ExceptionTo *to = exception->to();    
    ExceptionTo *to1 = to ? to->clone(network_) : nullptr;
    ExceptionPath *exception1 = exception->clone(from1, thrus1, to1, true);
    debugPrint(debug_, "exception_merge", 1, " split exception for {}",
               exception1->to_string(network_));
    addException1(exception1);

    ClockSet *clks2 = new ClockSet(*from->clks());
    ExceptionFrom *from2 = new ExceptionFrom(nullptr, clks2, nullptr,
                                             from->transition(), true, network_);
    ExceptionThruSeq *thrus2 = exceptionThrusClone(exception->thrus(), network_);
    ExceptionTo *to2 = to ? to->clone(network_) : nullptr;
    ExceptionPath *exception2 = exception->clone(from2, thrus2, to2, true);
    debugPrint(debug_, "exception_merge", 1, " split exception for {}",
               exception2->to_string(network_));
    addException1(exception2);

    delete exception;
  }
  else
    addException1(exception);
}

void
Sdc::addException1(ExceptionPath *exception)
{
  ExceptionTo *to = exception->to();
  if (to
      && (to->hasPins() || to->hasInstances())
      && to->hasClocks()) {
    ExceptionFrom *from1 = exception->from() ? exception->from()->clone(network_):nullptr;
    ExceptionThruSeq *thrus1 = exceptionThrusClone(exception->thrus(), network_);
    PinSet *pins1 = to->pins() ? new PinSet(*to->pins()) : nullptr;
    InstanceSet *insts1 = to->instances() ? new InstanceSet(*to->instances()) : nullptr;
    ExceptionTo *to1 = new ExceptionTo(pins1, nullptr, insts1, to->transition(),
                                       to->endTransition(), true, network_);
    ExceptionPath *exception1 = exception->clone(from1, thrus1, to1, true);
    debugPrint(debug_, "exception_merge", 1, " split exception for {}",
               exception1->to_string(network_));
    addException2(exception1);

    ExceptionFrom *from2 = exception->from() ? exception->from()->clone(network_):nullptr;
    ExceptionThruSeq *thrus2 = exceptionThrusClone(exception->thrus(), network_);
    ClockSet *clks2 = new ClockSet(*to->clks());
    ExceptionTo *to2 = new ExceptionTo(nullptr, clks2, nullptr, to->transition(),
                                       to->endTransition(), true, network_);
    ExceptionPath *exception2 = exception->clone(from2, thrus2, to2, true);
    debugPrint(debug_, "exception_merge", 1, " split exception for {}",
               exception2->to_string(network_));
    addException2(exception2);

    delete exception;
  }
  else
    addException2(exception);
}

void
Sdc::addException2(ExceptionPath *exception)
{
  if (exception->isMultiCycle() || exception->isPathDelay())
    deleteMatchingExceptions(exception);
  recordException(exception);
  mergeException(exception);
}

// If a path delay/multicycle exception is redefined with a different
// delay/cycle count, the new exception overrides the existing
// exception.  Multiple related exceptions are merged to reduce the
// number of tags.  To support overrides, relevant merged exceptions must be
// expanded to find and delete or override the new exception.
// For example, the exception
//   set_multi_cycle_path -from {A B} -to {C D} 2
// is a merged representation of the following four exceptions:
//   set_multi_cycle_path -from A -to C 2
//   set_multi_cycle_path -from A -to D 2
//   set_multi_cycle_path -from B -to C 2
//   set_multi_cycle_path -from B -to D 2
// If the following exception is later defined,
//   set_multi_cycle_path -from A -to C 3
// The cycle count of one of the merged exceptions changes.
// This prevents the original four exceptions from merging into one
// exception.
//
// This situation is handled by breaking the original merged exception
// into multiple smaller exceptions that exclude the new subset
// exception.  This is NOT done by expanding the merged exception,
// since the number of exception points can be huge leading to serious
// run time problems.
//
// For the example above, the merged exception is broken down into the
// following set of exceptions that exclude the new subset exception.
//
//   set_multi_cycle_path -from {B} -to {C D} 2
//   set_multi_cycle_path -from {A} -to {D} 2
//
// In general, the merged exception is broken down as follows:
//
//   -from {merged_from - subset_from} -thru merged_thru... -to merged_to
//   -from merged_from -thru {merged_thru - subset_thru}... -to merged_to
//   -from merged_from -thru merged_thru... -to {merged_to - subset_to}
//
// Where the {set1 - set2} is the set difference of of the from/thru/to
// objects of the merged/subset exception.  If the set difference is empty,
// that group of exceptions matches the subset so it should not be included
// in the expansion.

void
Sdc::deleteMatchingExceptions(ExceptionPath *exception)
{
  debugPrint(debug_, "exception_merge", 1, "find matches for {}",
             exception->to_string(network_));
  ExceptionPathSet matches;
  findMatchingExceptions(exception, matches);

  ExceptionPathSet expansions;
  for (ExceptionPath *match : matches)
    // Expand the matching exception into a set of exceptions that
    // that do not cover the new exception.  Do not record them
    // to prevent merging with the match, which will be deleted.
    expandExceptionExcluding(match, exception, expansions);

  for (ExceptionPath *match : matches)
    deleteException(match);

  for (ExceptionPath *match : expansions)
    addException(match);
}

void
Sdc::findMatchingExceptions(ExceptionPath *exception,
                            ExceptionPathSet &matches)
{
  if (exception->from())
    findMatchingExceptionsFirstFrom(exception, matches);
  else if (exception->thrus())
    findMatchingExceptionsFirstThru(exception, matches);
  else if (exception->to())
    findMatchingExceptionsFirstTo(exception, matches);
}

void
Sdc::findMatchingExceptionsFirstFrom(ExceptionPath *exception,
                                     ExceptionPathSet &matches)
{
  ExceptionFrom *from = exception->from();
  findMatchingExceptionsPins(exception, from->pins(),
                             first_from_pin_exceptions_,
                             matches);
  findMatchingExceptionsInsts(exception, from->instances(),
                              first_from_inst_exceptions_, matches);
  findMatchingExceptionsClks(exception, from->clks(),
                             first_from_clk_exceptions_,
                             matches);
}

void
Sdc::findMatchingExceptionsFirstThru(ExceptionPath *exception,
                                     ExceptionPathSet &matches)
{
  ExceptionThru *thru = (*exception->thrus())[0];
  findMatchingExceptionsPins(exception, thru->pins(),
                             first_thru_pin_exceptions_,
                             matches);
  findMatchingExceptionsInsts(exception, thru->instances(),
                              first_thru_inst_exceptions_,
                              matches);
  if (!first_thru_net_exceptions_.empty()
      && thru->nets()) {
    for (const Net *net : *thru->nets()) {
      // Potential matches includes exceptions that match net that are not
      // the first exception point.
      const ExceptionPathSet *potential_matches = 
        findKeyValuePtr(first_thru_net_exceptions_, net);
      if (potential_matches) {
        for (ExceptionPath *match : *potential_matches) {
          ExceptionThru *match_thru = (*match->thrus())[0];
          if (match_thru->nets()->contains(net)
              && match->overrides(exception)
              && match->intersectsPts(exception, network_))
            matches.insert(match);
        }
      }
    }
  }
}

void
Sdc::findMatchingExceptionsFirstTo(ExceptionPath *exception,
                                   ExceptionPathSet &matches)
{
  ExceptionTo *to = exception->to();
  findMatchingExceptionsPins(exception, to->pins(), first_to_pin_exceptions_,
                             matches);
  findMatchingExceptionsInsts(exception, to->instances(),
                              first_to_inst_exceptions_,
                              matches);
  findMatchingExceptionsClks(exception, to->clks(), first_to_clk_exceptions_,
                             matches);
}

void
Sdc::findMatchingExceptionsClks(ExceptionPath *exception,
                                ClockSet *clks,
                                ClockExceptionsMap &exception_map,
                                ExceptionPathSet &matches)
{
  if (clks) {
    ExceptionPathSet clks_matches;
    for (Clock *clk : *clks) {
      auto itr = exception_map.find(clk);
      if (itr != exception_map.end())
        clks_matches.insert(itr->second.begin(), itr->second.end());
    }
    findMatchingExceptions(exception, &clks_matches, matches);
  }
}

void
Sdc::findMatchingExceptionsPins(ExceptionPath *exception,
                                PinSet *pins,
                                PinExceptionsMap &exception_map,
                                ExceptionPathSet &matches)
{
  if (pins) {
    ExceptionPathSet pins_matches;
    for (const Pin *pin : *pins) {
      auto itr = exception_map.find(pin);
      if (itr != exception_map.end())
        pins_matches.insert(itr->second.begin(), itr->second.end());
    }
    findMatchingExceptions(exception, &pins_matches, matches);
  }
}

void
Sdc::findMatchingExceptionsInsts(ExceptionPath *exception,
                                 InstanceSet *insts,
                                 InstanceExceptionsMap &exception_map,
                                 ExceptionPathSet &matches)
{
  if (insts) {
    ExceptionPathSet inst_matches;
    for (const Instance *inst : *insts) {
      auto itr = exception_map.find(inst);
      if (itr != exception_map.end())
        inst_matches.insert(itr->second.begin(), itr->second.end());
    }
    findMatchingExceptions(exception, &inst_matches, matches);
  }
}

void
Sdc::findMatchingExceptions(ExceptionPath *exception,
                            ExceptionPathSet *potential_matches,
                            ExceptionPathSet &matches)
{
  if (potential_matches) {
    for (ExceptionPath *match : *potential_matches) {
      if (match->overrides(exception)
          && match->intersectsPts(exception, network_))
        matches.insert(match);
    }
  }
}

void
Sdc::expandExceptionExcluding(ExceptionPath *exception,
                              ExceptionPath *excluding,
                              ExceptionPathSet &expansions)
{
  ExceptionFrom *from = exception->from();
  ExceptionThruSeq *thrus = exception->thrus();
  ExceptionTo *to = exception->to();
  if (from) {
    ExceptionFrom *from_cpy = from->clone(network_);
    from_cpy->deleteObjects(excluding->from(), network_);
    if (from_cpy->hasObjects()) {
      ExceptionThruSeq *thrus_cpy = nullptr;
      if (thrus)
        thrus_cpy = clone(thrus, network_);
      ExceptionTo *to_cpy = nullptr;
      if (to)
        to_cpy = to->clone(network_);
      ExceptionPath *expand = exception->clone(from_cpy,thrus_cpy,to_cpy,true);
      expansions.insert(expand);
    }
    else
      delete from_cpy;
  }
  if (thrus) {
    ExceptionThruSeq *excluding_thrus = excluding->thrus();
    ExceptionThruSeq::iterator thru_iter = thrus->begin();
    ExceptionThruSeq::iterator thru_iter2 = excluding_thrus->begin();
    while (thru_iter != thrus->end()
           && thru_iter2 != excluding_thrus->end()) {
      ExceptionThru *thru = *thru_iter++;
      ExceptionThru *thru2 = *thru_iter2++;
      ExceptionThru *thru_cpy = thru->clone(network_);
      thru_cpy->deleteObjects(thru2, network_);
      if (thru_cpy->hasObjects()) {
        ExceptionFrom *from_cpy = nullptr;
        if (from)
          from_cpy = from->clone(network_);
        ExceptionThruSeq *thrus_cpy = new ExceptionThruSeq;
        for (ExceptionThru *thru1 : *thrus) {
          if (thru1 == thru)
            thrus_cpy->push_back(thru_cpy);
          else {
            ExceptionThru *thru_cpy = thru->clone(network_);
            thrus_cpy->push_back(thru_cpy);
          }
        }
        ExceptionTo *to_cpy = nullptr;
        if (to)
          to_cpy = to->clone(network_);
        ExceptionPath *expand = exception->clone(from_cpy, thrus_cpy, to_cpy,
                                                 true);
        expansions.insert(expand);
      }
      else
        delete thru_cpy;
    }
  }
  if (to) {
    ExceptionTo *to_cpy = to->clone(network_);
    to_cpy->deleteObjects(excluding->to(), network_);
    if (to_cpy->hasObjects()) {
      ExceptionFrom *from_cpy = nullptr;
      if (from)
        from_cpy = from->clone(network_);
      ExceptionThruSeq *thrus_cpy = nullptr;
      if (thrus)
        thrus_cpy = clone(thrus, network_);
      ExceptionPath *expand = exception->clone(from_cpy,thrus_cpy,to_cpy,true);
      expansions.insert(expand);
    }
    else
      delete to_cpy;
  }
}

static ExceptionThruSeq *
clone(ExceptionThruSeq *thrus,
      Network *network)
{
  ExceptionThruSeq *thrus_cpy = new ExceptionThruSeq;
  for (ExceptionThru *thru : *thrus) {
    ExceptionThru *thru_cpy = thru->clone(network);
    thrus_cpy->push_back(thru_cpy);
  }
  return thrus_cpy;
}

////////////////////////////////////////////////////////////////

void
Sdc::recordException(ExceptionPath *exception)
{
  exceptions_.insert(exception);
  exception->setId(++exception_id_);
  recordMergeHashes(exception);
  recordExceptionFirstPts(exception);
  recordExceptionPins(exception);
  checkForThruHpins(exception);
}

void
Sdc::checkForThruHpins(ExceptionPath *exception)
{
  ExceptionThruSeq *thrus = exception->thrus();
  if (thrus) {
    for (ExceptionThru *thru : *thrus) {
      if (thru->edges()) {
        have_thru_hpin_exceptions_ = true;
        break;
      }
    }
  }
}

void
Sdc::recordMergeHashes(ExceptionPath *exception)
{
  ExceptionPtIterator missing_pt_iter(exception);
  while (missing_pt_iter.hasNext()) {
    ExceptionPt *missing_pt = missing_pt_iter.next();
    recordMergeHash(exception, missing_pt);
  }
}

void
Sdc::recordMergeHash(ExceptionPath *exception,
                     ExceptionPt *missing_pt)
{
  size_t hash = exception->hash(missing_pt);
  debugPrint(debug_, "exception_merge", 3,
             "record merge hash {} {} missing {}",
             hash,
             exception->to_string(network_),
             missing_pt->to_string(network_));
  ExceptionPathSet &set = exception_merge_hash_[hash];
  set.insert(exception);
}

// Record a mapping from first pin/clock/instance's to a set of exceptions.
// The first exception point is when the exception becomes active.
// After it becomes active, its state changes as the other
// exception points are traversed.
void
Sdc::recordExceptionFirstPts(ExceptionPath *exception)
{
  if (exception->from())
    recordExceptionFirstFrom(exception);
  else if (exception->thrus())
    recordExceptionFirstThru(exception);
  else if (exception->to())
    recordExceptionFirstTo(exception);
}

void
Sdc::recordExceptionFirstFrom(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  recordExceptionPins(exception, from->pins(), first_from_pin_exceptions_);
  recordExceptionInsts(exception, from->instances(),
                       first_from_inst_exceptions_);
  recordExceptionClks(exception, from->clks(), first_from_clk_exceptions_);
}

void
Sdc::recordExceptionPins(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  if (from)
    recordExceptionPins(exception, from->pins(), pin_exceptions_);
  ExceptionThruSeq *thrus = exception->thrus();
  if (thrus) {
    for (ExceptionThru *thru : *thrus)
      recordExceptionPins(exception, thru->pins(), pin_exceptions_);
  }
  ExceptionTo *to = exception->to();
  if (to)
    recordExceptionPins(exception, to->pins(), pin_exceptions_);
}

void
Sdc::recordExceptionFirstThru(ExceptionPath *exception)
{
  ExceptionThru *thru = (*exception->thrus())[0];
  recordExceptionPins(exception, thru->pins(), first_thru_pin_exceptions_);
  recordExceptionInsts(exception, thru->instances(),
                       first_thru_inst_exceptions_);
  recordExceptionEdges(exception, thru->edges(), first_thru_edge_exceptions_);
  for (ExceptionThru *thru : *exception->thrus()) 
    recordExceptionNets(exception, thru->nets(), first_thru_net_exceptions_);
}

void
Sdc::recordExceptionFirstTo(ExceptionPath *exception)
{
  ExceptionTo *to = exception->to();
  recordExceptionPins(exception, to->pins(), first_to_pin_exceptions_);
  recordExceptionInsts(exception, to->instances(), first_to_inst_exceptions_);
  recordExceptionClks(exception, to->clks(), first_to_clk_exceptions_);
}

void
Sdc::recordExceptionClks(ExceptionPath *exception,
                         ClockSet *clks,
                         ClockExceptionsMap &exception_map)
{
  if (clks) {
    for (Clock *clk : *clks) {
      ExceptionPathSet &set = exception_map[clk];
      set.insert(exception);
    }
  }
}

void
Sdc::recordExceptionEdges(ExceptionPath *exception,
                          EdgePinsSet *edges,
                          EdgeExceptionsMap &exception_map)
{
  if (edges) {
    for (const EdgePins &edge : *edges) {
      ExceptionPathSet &set = exception_map[edge];
      set.insert(exception);
    }
  }
}

void
Sdc::recordExceptionPins(ExceptionPath *exception,
                         PinSet *pins,
                         PinExceptionsMap &exception_map)
{
  if (pins) {
    for (const Pin *pin : *pins) {
      ExceptionPathSet &set = exception_map[pin];
      set.insert(exception);
    }
  }
}

void
Sdc::recordExceptionHpin(ExceptionPath *exception,
                         Pin *pin,
                         PinExceptionsMap &exception_map)
{
  ExceptionPathSet &set = exception_map[pin];
  set.insert(exception);
}

void
Sdc::recordExceptionInsts(ExceptionPath *exception,
                          InstanceSet *insts,
                          InstanceExceptionsMap &exception_map)
{
  if (insts) {
    for (const Instance *inst : *insts) {
      ExceptionPathSet &set = exception_map[inst];
      set.insert(exception);
    }
  }
}

void
Sdc::recordExceptionNets(ExceptionPath *exception,
                         NetSet *nets,
                         NetExceptionsMap &exception_map)
{
  if (nets) {
    for (const Net *net : *nets) {
      ExceptionPathSet &set = exception_map[net];
      set.insert(exception);
    }
  }
}

// Exceptions of the same type can be merged if they differ in exactly
// one exception point (-from, -thru or -to).
// For example, the following exceptions:
//   set_false_path -from {A B} -to C
//   set_false_path -from {A B} -to D
// can be merged to form:
//   set_false_path -from {A B} -to {C D}
//
// A hash is generated for each exception missing one exception point
// to find potential matches.  If a match is found, the exceptions are
// merged. Next we try to merge the surviving exception until we run
// out of merges.
void
Sdc::mergeException(ExceptionPath *exception)
{
  ExceptionPath *merged = findMergeMatch(exception);
  while (merged)
    merged = findMergeMatch(merged);
}

// Return the merged result.
ExceptionPath *
Sdc::findMergeMatch(ExceptionPath *exception)
{
  bool first_pt = true;
  ExceptionPtIterator missing_pt_iter(exception);
  while (missing_pt_iter.hasNext()) {
    ExceptionPt *missing_pt = missing_pt_iter.next();
    size_t hash = exception->hash(missing_pt);
    auto itr = exception_merge_hash_.find(hash);
    if (itr != exception_merge_hash_.end()) {
      ExceptionPathSet &matches = itr->second;
      for (ExceptionPath *match : matches) {
        ExceptionPt *match_missing_pt;
        if (match != exception
            // Exceptions are not merged if their priorities are
            // different.  This allows exceptions to be pruned during
            // search at the endpoint.
            && exception->mergeable(match)
            && match->mergeablePts(exception, missing_pt, match_missing_pt)) {
          debugPrint(debug_, "exception_merge", 1, "merge {}",
                     exception->to_string(network_));
          debugPrint(debug_, "exception_merge", 1, " with {}",
                     match->to_string(network_));
          // Unrecord the exception that is being merged away.
          unrecordException(exception);
          unrecordMergeHashes(match);
          missing_pt->mergeInto(match_missing_pt, network_);
          recordMergeHashes(match);
          // First point maps only change if the exception point that
          // is being merged is the first exception point.
          if (first_pt)
            recordExceptionFirstPts(match);
          // Have to wait until after exception point merge to delete
          // the exception.
          delete exception;
          return match;
        }
      }
    }
    first_pt = false;
  }
  return nullptr;
}

////////////////////////////////////////////////////////////////

void
Sdc::deleteExceptions()
{
  for (ExceptionPath *exception : exceptions_)
    delete exception;
  exceptions_.clear();
  exception_id_ = 0;

  first_from_pin_exceptions_.clear();
  first_from_clk_exceptions_.clear();
  first_from_inst_exceptions_.clear();
  first_to_pin_exceptions_.clear();
  first_to_clk_exceptions_.clear();
  first_to_inst_exceptions_.clear();
  first_thru_pin_exceptions_.clear();
  first_thru_inst_exceptions_.clear();
  first_thru_net_exceptions_.clear();
  first_thru_edge_exceptions_.clear();
  first_thru_edge_exceptions_.clear();
  path_delay_internal_from_.clear();
  path_delay_internal_from_break_.clear();
  path_delay_internal_to_.clear();
  path_delay_internal_to_break_.clear();
  pin_exceptions_.clear();

  exception_merge_hash_.clear();
  exception_merge_hash_.clear();
  have_thru_hpin_exceptions_ = false;
}

////////////////////////////////////////////////////////////////

void
Sdc::deleteExceptionsReferencing(Clock *clk)
{
  // erase prevents range iteration.
  for (auto itr = exceptions_.begin(); itr != exceptions_.end(); ) {
    ExceptionPath *exception = *itr;
    bool deleted = false;
    ExceptionFrom *from = exception->from();
    if (from) {
      ClockSet *clks = from->clks();
      if (clks && clks->contains(clk)) {
        itr = exceptions_.erase(itr);
        unrecordException(exception);
        deleted = true;
        from->deleteClock(clk);
        if (from->hasObjects())
          recordException(exception);
        else
          deleteException(exception);
      }
    }

    if (!deleted) {
      ExceptionTo *to = exception->to();
      if (to) {
        ClockSet *clks = to->clks();
        if (clks && clks->contains(clk)) {
          itr = exceptions_.erase(itr);
          deleted = true;
          unrecordException(exception);
          to->deleteClock(clk);
          if (to->hasObjects())
            recordException(exception);
          else
            deleteException(exception);
        }
      }
    }
    if (!deleted)
      itr++;
  }
}

void
Sdc::deleteException(ExceptionPath *exception)
{
  debugPrint(debug_, "exception_merge", 2, "delete {}",
             exception->to_string(network_));
  unrecordException(exception);
  delete exception;
}

void
Sdc::unrecordException(ExceptionPath *exception)
{
  unrecordMergeHashes(exception);
  unrecordExceptionFirstPts(exception);
  unrecordExceptionPins(exception);
  exceptions_.erase(exception);
}

void
Sdc::unrecordMergeHashes(ExceptionPath *exception)
{
  ExceptionPtIterator missing_pt_iter(exception);
  while (missing_pt_iter.hasNext()) {
    ExceptionPt *missing_pt = missing_pt_iter.next();
    unrecordMergeHash(exception, missing_pt);
  }
}

void
Sdc::unrecordMergeHash(ExceptionPath *exception,
                       ExceptionPt *missing_pt)
{
  size_t hash = exception->hash(missing_pt);
  debugPrint(debug_, "exception_merge", 3,
             "unrecord merge hash {} {} missing {}",
             hash,
             exception->to_string(network_),
             missing_pt->to_string(network_));
  auto itr = exception_merge_hash_.find(hash);
  if (itr != exception_merge_hash_.end()) {
    ExceptionPathSet &matches = itr->second;
    matches.erase(exception);
  }
}

void
Sdc::unrecordExceptionFirstPts(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  ExceptionThruSeq *thrus = exception->thrus();
  ExceptionTo *to = exception->to();
  if (from) {
    unrecordExceptionPins(exception, from->pins(), first_from_pin_exceptions_);
    unrecordExceptionClks(exception, from->clks(), first_from_clk_exceptions_);
    unrecordExceptionInsts(exception, from->instances(), first_from_inst_exceptions_);
  }
  else if (thrus) {
    ExceptionThru *thru = (*thrus)[0];
    unrecordExceptionPins(exception, thru->pins(), first_thru_pin_exceptions_);
    unrecordExceptionInsts(exception, thru->instances(),
                           first_thru_inst_exceptions_);
    unrecordExceptionNets(exception, thru->nets(), first_thru_net_exceptions_);
    unrecordExceptionEdges(exception, thru->edges(),
                           first_thru_edge_exceptions_);
  }
  else if (to) {
    unrecordExceptionPins(exception, to->pins(), first_to_pin_exceptions_);
    unrecordExceptionClks(exception, to->clks(), first_to_clk_exceptions_);
    unrecordExceptionInsts(exception, to->instances(),
                           first_to_inst_exceptions_);
  }
}

void
Sdc::unrecordExceptionPins(ExceptionPath *exception)
{
  ExceptionFrom *from = exception->from();
  if (from)
    unrecordExceptionPins(exception, from->pins(), pin_exceptions_);
  ExceptionThruSeq *thrus = exception->thrus();
  if (thrus) {
    for (ExceptionThru *thru : *thrus)
      unrecordExceptionPins(exception, thru->pins(), pin_exceptions_);
  }
  ExceptionTo *to = exception->to();
  if (to)
    unrecordExceptionPins(exception, to->pins(), pin_exceptions_);
}

void
Sdc::unrecordExceptionClks(ExceptionPath *exception,
                           ClockSet *clks,
                           ClockExceptionsMap &exception_map)
{
  if (clks) {
    for (Clock *clk : *clks) {
      auto itr = exception_map.find(clk);
      if (itr != exception_map.end()) {
        ExceptionPathSet &set = itr->second;
        set.erase(exception);
      }
    }
  }
}

void
Sdc::unrecordExceptionPins(ExceptionPath *exception,
                           PinSet *pins,
                           PinExceptionsMap &exception_map)
{
  if (pins) {
    for (const Pin *pin : *pins) {
      auto itr = exception_map.find(pin);
      if (itr != exception_map.end()) {
        ExceptionPathSet &set = itr->second;
        set.erase(exception);
      }
    }
  }
}

void
Sdc::unrecordExceptionInsts(ExceptionPath *exception,
                            InstanceSet *insts,
                            InstanceExceptionsMap &exception_map)
{
  if (insts) {
    for (const Instance *inst : *insts) {
      auto itr = exception_map.find(inst);
      if (itr != exception_map.end()) {
        ExceptionPathSet &set = itr->second;
        set.erase(exception);
      }
    }
  }
}

void
Sdc::unrecordExceptionEdges(ExceptionPath *exception,
                            EdgePinsSet *edges,
                            EdgeExceptionsMap &exception_map)
{
  if (edges) {
    for (const EdgePins &edge : *edges) {
      auto itr = exception_map.find(edge);
      if (itr != exception_map.end()) {
        ExceptionPathSet &set = itr->second;
        set.erase(exception);
      }
    }
  }
}

void
Sdc::unrecordExceptionNets(ExceptionPath *exception,
                           NetSet *nets,
                           NetExceptionsMap &exception_map)
{
  if (nets) {
    for (const Net *net : *nets) {
      auto itr = exception_map.find(net);
      if (itr != exception_map.end()) {
        ExceptionPathSet &set = itr->second;
        set.erase(exception);
      }
    }
  }
}

void
Sdc::unrecordExceptionHpin(ExceptionPath *exception,
                           Pin *pin,
                           PinExceptionsMap &exception_map)
{
  auto itr = exception_map.find(pin);
  if (itr != exception_map.end()) {
    ExceptionPathSet &set = itr->second;
    set.erase(exception);
  }
}

////////////////////////////////////////////////////////////////

class ExpandException : public ExpandedExceptionVisitor
{
public:
  ExpandException(ExceptionPath *exception,
                  ExceptionPathSet &expansions,
                  Network *network);
  void visit(ExceptionFrom *from,
             ExceptionThruSeq *thrus,
             ExceptionTo *to) override;

private:
  ExceptionPathSet &expansions_;
};

ExpandException::ExpandException(ExceptionPath *exception,
                                 ExceptionPathSet &expansions,
                                 Network *network) :
  ExpandedExceptionVisitor(exception, network),
  expansions_(expansions)
{
}

void
ExpandException::visit(ExceptionFrom *from,
                       ExceptionThruSeq *thrus,
                       ExceptionTo *to)
{
  ExceptionFrom *from_clone = nullptr;
  if (from)
    from_clone = from->clone(network_);
  ExceptionThruSeq *thrus_clone = nullptr;
  if (thrus) {
    thrus_clone = new ExceptionThruSeq;
    for (ExceptionThru *thru : *thrus) {
      ExceptionThru *thru_clone = thru->clone(network_);
      thrus_clone->push_back(thru_clone);
    }
  }
  ExceptionTo *to_clone = nullptr;
  if (to)
    to_clone = to->clone(network_);
  ExceptionPath *expand = exception_->clone(from_clone, thrus_clone,
                                            to_clone, true);
  expansions_.insert(expand);
}

// Expand exception from/thrus/to sets so there is only one exception
// point in each from/thru/to.
void
Sdc::expandException(ExceptionPath *exception,
                     ExceptionPathSet &expansions)
{
  ExpandException expander(exception, expansions, network_);
  expander.visitExpansions();
}

////////////////////////////////////////////////////////////////

void
Sdc::resetPath(ExceptionFrom *from,
               ExceptionThruSeq *thrus,
               ExceptionTo *to,
               const MinMaxAll *min_max)
{
  checkFromThrusTo(from, thrus, to);
  // erase prevents range iteration.
  for (auto itr = exceptions_.begin(); itr != exceptions_.end(); ) {
    ExceptionPath *match = *itr;
    if (match->resetMatch(from, thrus, to, min_max, network_)) {
      debugPrint(debug_, "exception_match", 3, "reset match {}",
                 match->to_string(network_));
      ExceptionPathSet expansions;
      expandException(match, expansions);
      itr = exceptions_.erase(itr);
      deleteException(match);

      for (ExceptionPath *expand : expansions) {
        if (expand->resetMatch(from, thrus, to, min_max, network_)) {
          unrecordPathDelayInternalFrom(expand);
          unrecordPathDelayInternalTo(expand);
          delete expand;
        }
        else
          addException(expand);
      }
    }
    else
      itr++;
  }
}

////////////////////////////////////////////////////////////////

bool
Sdc::exceptionFromStates(const Pin *pin,
                         const RiseFall *rf,
                         const Clock *clk,
                         const RiseFall *clk_rf,
                         const MinMax *min_max,
                         ExceptionStateSet *&states)
{
  return exceptionFromStates(pin, rf, clk, clk_rf, min_max, true, states);
}

bool
Sdc::exceptionFromStates(const Pin *pin,
                         const RiseFall *rf,
                         const Clock *clk,
                         const RiseFall *clk_rf,
                         const MinMax *min_max,
                         bool include_filter,
                         ExceptionStateSet *&states)
{
  bool srch_from = true;
  if (pin) {
    if (srch_from) {
      const ExceptionPathSet *exceptions =
        findKeyValuePtr(first_from_pin_exceptions_, pin);
      srch_from &= exceptionFromStates(exceptions, pin, rf, min_max,
                                       include_filter, states);
    }
    if (srch_from) {
      const ExceptionPathSet *exceptions =
        findKeyValuePtr(first_thru_pin_exceptions_, pin);
      srch_from &= exceptionFromStates(exceptions, pin, rf, min_max,
                                       include_filter, states);
    }
    if (srch_from
        && (!first_from_inst_exceptions_.empty()
            || !first_thru_inst_exceptions_.empty())) {
      Instance *inst = network_->instance(pin);
      const ExceptionPathSet *exceptions =
        findKeyValuePtr(first_from_inst_exceptions_, inst);
      srch_from &= exceptionFromStates(exceptions, pin, rf, min_max,
                                       include_filter, states);
      const ExceptionPathSet *exceptions2 =
        findKeyValuePtr(first_thru_inst_exceptions_, inst);
      srch_from &= exceptionFromStates(exceptions2, pin, rf, min_max,
                                       include_filter, states);
    }
  }
  if (srch_from && clk) {
    const ExceptionPathSet *exceptions = findKeyValuePtr(first_from_clk_exceptions_, clk);
    srch_from &= exceptionFromStates(exceptions, pin, clk_rf, min_max,
                                     include_filter, states);
  }
  if (!srch_from) {
    delete states;
    states = nullptr;
  }
  return srch_from;
}

bool
Sdc::exceptionFromStates(const ExceptionPathSet *exceptions,
                         const Pin *pin,
                         const RiseFall *rf,
                         const MinMax *min_max,
                         bool include_filter,
                         ExceptionStateSet *&states) const
{
  if (exceptions) {
    for (ExceptionPath *exception : *exceptions) {
      if (exception->matches(min_max, false)
          && (exception->from() == nullptr
              || exception->from()->transition()->matches(rf))
          && (include_filter || !exception->isFilter())) {
        ExceptionState *state = exception->firstState();
        if (state->matchesNextThru(nullptr, pin, rf, min_max, network_))
          // -from clk -thru reg/clk
          state = state->nextState();
        // If the exception is -from and has no -to transition it is
        // complete out of the gate.
        if (state->isComplete()
            && exception->isFalse()) {
          // Leave the completed false path state as a marker on the tag,
          // but flush all other exception states because they are lower
          // priority.
          if (states == nullptr)
            states = new ExceptionStateSet();
          states->clear();
          states->insert(state);
          // No need to examine other exceptions from this
          // pin/clock/instance.
          return false;
        }
        if (states == nullptr)
          states = new ExceptionStateSet();
        states->insert(state);
      }
    }
  }
  return true;
}

void
Sdc::exceptionFromClkStates(const Pin *pin,
                            const RiseFall *rf,
                            const Clock *clk,
                            const RiseFall *clk_rf,
                            const MinMax *min_max,
                            ExceptionStateSet *&states)
{
  if (pin) {
    const ExceptionPathSet *exceptions =
      findKeyValuePtr(first_from_pin_exceptions_, pin);
    exceptionFromStates(exceptions, nullptr, rf, min_max, true, states);
    if (!first_from_inst_exceptions_.empty()) {
      Instance *inst = network_->instance(pin);
      const ExceptionPathSet *exceptions =
        findKeyValuePtr(first_from_inst_exceptions_, inst);
      exceptionFromStates(exceptions, pin, rf, min_max, true, states);
    }
    const ExceptionPathSet *exceptions2 = findKeyValuePtr(first_thru_pin_exceptions_, pin);
    exceptionThruStates(exceptions2, rf, min_max, states);
  }
  const ExceptionPathSet *exceptions = findKeyValuePtr(first_from_clk_exceptions_, clk);
  exceptionFromStates(exceptions, pin, clk_rf, min_max, true, states);
}

void
Sdc::filterRegQStates(const Pin *to_pin,
                      const RiseFall *to_rf,
                      const MinMax *min_max,
                      ExceptionStateSet *&states) const
{
  if (!first_from_pin_exceptions_.empty()) {
    auto itr = first_from_pin_exceptions_.find(to_pin);
    if (itr != first_from_pin_exceptions_.end()) {
      const ExceptionPathSet &exceptions = itr->second;
      for (ExceptionPath *exception : exceptions) {
        // Hack for filter -from reg/Q.
        if (exception->isFilter()
            && exception->matchesFirstPt(to_rf, min_max)) {
          ExceptionState *state = exception->firstState();
          if (states == nullptr)
            states = new ExceptionStateSet();
          states->insert(state);
        }
      }
    }
  }
}

void
Sdc::exceptionThruStates(const Pin *from_pin,
                         const Pin *to_pin,
                         const RiseFall *to_rf,
                         const MinMax *min_max,
                         ExceptionStateSet *&states)
{
  const ExceptionPathSet *exceptions =
    findKeyValuePtr(first_thru_pin_exceptions_, to_pin);
  exceptionThruStates(exceptions, to_rf, min_max, states);

  if (!first_thru_edge_exceptions_.empty()) {
    EdgePins edge_pins(from_pin, to_pin);
    auto itr = first_thru_edge_exceptions_.find(edge_pins);
    if (itr != first_thru_edge_exceptions_.end()) {
      const ExceptionPathSet *exceptions = &itr->second;
      exceptionThruStates(exceptions, to_rf, min_max, states);
    }
  }
  if (!first_thru_inst_exceptions_.empty()
      && (network_->direction(to_pin)->isAnyOutput()
          || network_->isLatchData(to_pin))) {
    const Instance *to_inst = network_->instance(to_pin);
    const ExceptionPathSet *exceptions =
      findKeyValuePtr(first_thru_inst_exceptions_, to_inst);
    exceptionThruStates(exceptions, to_rf, min_max, states);
  }
}

void
Sdc::exceptionThruStates(const ExceptionPathSet *exceptions,
                         const RiseFall *to_rf,
                         const MinMax *min_max,
                         // Return value.
                         ExceptionStateSet *&states) const
{
  if (exceptions) {
    for (ExceptionPath *exception : *exceptions) {
      if (exception->matchesFirstPt(to_rf, min_max)) {
        ExceptionState *state = exception->firstState();
        if (states == nullptr)
          states = new ExceptionStateSet();
        states->insert(state);
      }
    }
  }
}

////////////////////////////////////////////////////////////////

void
Sdc::exceptionTo(ExceptionPathType type,
                 const Pin *pin,
                 const RiseFall *rf,
                 const ClockEdge *clk_edge,
                 const MinMax *min_max,
                 bool match_min_max_exactly,
                 // Return values.
                 ExceptionPath *&hi_priority_exception,
                 int &hi_priority)
{
  if (!first_to_inst_exceptions_.empty()) {
    Instance *inst = network_->instance(pin);
    const ExceptionPathSet *exceptions = findKeyValuePtr(first_to_inst_exceptions_, inst);
    exceptionTo(exceptions, type, pin, rf,
                clk_edge, min_max, match_min_max_exactly,
                hi_priority_exception, hi_priority);
  }
  if (!first_to_pin_exceptions_.empty()) {
    const ExceptionPathSet *exceptions = findKeyValuePtr(first_to_pin_exceptions_, pin);
    exceptionTo(exceptions, type, pin, rf,
                clk_edge, min_max, match_min_max_exactly,
                hi_priority_exception, hi_priority);
  }
  if (clk_edge && !first_to_clk_exceptions_.empty()) {
    const ExceptionPathSet *exceptions =
      findKeyValuePtr(first_to_clk_exceptions_, clk_edge->clock());
    exceptionTo(exceptions, type, pin, rf, clk_edge,
                min_max, match_min_max_exactly,
                hi_priority_exception, hi_priority);
  }
}

void
Sdc::exceptionTo(const ExceptionPathSet *to_exceptions,
                 ExceptionPathType type,
                 const Pin *pin,
                 const RiseFall *rf,
                 const ClockEdge *clk_edge,
                 const MinMax *min_max,
                 bool match_min_max_exactly,
                 // Return values.
                 ExceptionPath *&hi_priority_exception,
                 int &hi_priority) const
{
  if (to_exceptions) {
    for (ExceptionPath *exception : *to_exceptions) {
      exceptionTo(exception, type, pin, rf, clk_edge,
                  min_max, match_min_max_exactly,
                  hi_priority_exception, hi_priority);
    }
  }
}

void
Sdc::exceptionTo(ExceptionPath *exception,
                 ExceptionPathType type,
                 const Pin *pin,
                 const RiseFall *rf,
                 const ClockEdge *clk_edge,
                 const MinMax *min_max,
                 bool match_min_max_exactly,
                 // Return values.
                 ExceptionPath *&hi_priority_exception,
                 int &hi_priority) const
{
  if ((type == ExceptionPathType::any
       || exception->type() == type)
      && exceptionMatchesTo(exception, pin, rf, clk_edge, min_max,
                            match_min_max_exactly, false)) {
    int priority = exception->priority(min_max);
    if (hi_priority_exception == nullptr
        || priority > hi_priority
        || (priority == hi_priority
            && exception->tighterThan(hi_priority_exception))) {
      hi_priority = priority;
      hi_priority_exception = exception;
    }
  }
}

bool
Sdc::exceptionMatchesTo(ExceptionPath *exception,
                        const Pin *pin,
                        const RiseFall *rf,
                        const ClockEdge *clk_edge,
                        const MinMax *min_max,
                        bool match_min_max_exactly,
                        bool require_to_pin) const
{
  ExceptionTo *to = exception->to();
  return exception->matches(min_max, match_min_max_exactly)
    && ((to == nullptr
         && !require_to_pin)
        || (to
            && to->matches(pin, clk_edge, rf, network_)));
}

bool
Sdc::isCompleteTo(ExceptionState *state,
                  const Pin *pin,
                  const RiseFall *rf,
                  const ClockEdge *clk_edge,
                  const MinMax *min_max,
                  bool match_min_max_exactly,
                  bool require_to_pin) const
{
  return state->nextThru() == nullptr
    && exceptionMatchesTo(state->exception(), pin, rf, clk_edge,
                          min_max, match_min_max_exactly, require_to_pin);
}

bool
Sdc::isCompleteTo(ExceptionState *state,
                  const Pin *pin,
                  const RiseFall *rf,
                  const MinMax *min_max) const
{
  ExceptionPath *exception = state->exception();
  ExceptionTo *to = exception->to();
  return state->nextThru() == nullptr
    && to
    && exception->matches(min_max, true)
    && to->matches(pin, rf, network_);
}

////////////////////////////////////////////////////////////////

void
Sdc::groupPathsTo(const Pin *pin,
                  const RiseFall *rf,
                  const ClockEdge *clk_edge,
                  const MinMax *min_max,
                  // Return value.
                  ExceptionPathSeq &group_paths)
{
  if (!first_to_inst_exceptions_.empty()) {
    Instance *inst = network_->instance(pin);
    const ExceptionPathSet *exceptions = findKeyValuePtr(first_to_inst_exceptions_, inst);
    groupPathsTo(exceptions, pin, rf, clk_edge, min_max, group_paths);
  }
  if (!first_to_pin_exceptions_.empty()) {
    const ExceptionPathSet *exceptions = findKeyValuePtr(first_to_pin_exceptions_, pin);
    groupPathsTo(exceptions, pin, rf, clk_edge, min_max, group_paths);
  }
  if (clk_edge && !first_to_clk_exceptions_.empty()) {
    const ExceptionPathSet *exceptions =
      findKeyValuePtr(first_to_clk_exceptions_, clk_edge->clock());
    groupPathsTo(exceptions, pin, rf, clk_edge, min_max, group_paths);
  }
}

void
Sdc::groupPathsTo(const ExceptionPathSet *to_exceptions,
                  const Pin *pin,
                  const RiseFall *rf,
                  const ClockEdge *clk_edge,
                  const MinMax *min_max,
                  // Return value.
                  ExceptionPathSeq &group_paths) const
{
  if (to_exceptions) {
    for (ExceptionPath *exception : *to_exceptions) {
      if (exception->isGroupPath()
          && exceptionMatchesTo(exception, pin, rf, clk_edge, min_max, true, false))
        group_paths.push_back(exception);
    }
  }
}

////////////////////////////////////////////////////////////////

Wireload *
Sdc::wireload(const MinMax *min_max) const
{
  return wireload_[min_max->index()];
}

void
Sdc::setWireload(Wireload *wireload,
                 const MinMaxAll *min_max)
{
  for (auto mm_index : min_max->rangeIndex())
    wireload_[mm_index] = wireload;
}

void
Sdc::setWireloadMode(WireloadMode mode)
{
  wireload_mode_ = mode;
}

const WireloadSelection *
Sdc::wireloadSelection(const MinMax *min_max)
{
  const WireloadSelection *sel = wireload_selection_[min_max->index()];
  if (sel == nullptr) {
    // Look for a default.
    LibertyLibrary *lib = network_->defaultLibertyLibrary();
    if (lib) {
      const WireloadSelection *default_sel = lib->defaultWireloadSelection();
      if (default_sel) {
        sel = default_sel;
        setWireloadSelection(default_sel, MinMaxAll::all());
      }
    }
  }
  return sel;
}

void
Sdc::setWireloadSelection(const WireloadSelection *selection,
                          const MinMaxAll *min_max)
{
  for (auto mm_index : min_max->rangeIndex())
    wireload_selection_[mm_index] = selection;
}

////////////////////////////////////////////////////////////////

void
Sdc::connectPinAfter(const Pin *pin)
{
  if (have_thru_hpin_exceptions_) {
    PinSet *drvrs = network_->drivers(pin);
    for (ExceptionPath *exception : exceptions_) {
      ExceptionPt *first_pt = exception->firstPt();
      ExceptionThruSeq *thrus = exception->thrus();
      if (thrus) {
        for (ExceptionThru *thru : *exception->thrus()) {
          if (thru->edges()) {
            thru->connectPinAfter(drvrs, network_);
            if (first_pt == thru)
              recordExceptionEdges(exception, thru->edges(),
                                   first_thru_edge_exceptions_);
          }
        }
      }
    }
  }
}

void
Sdc::deletePinBefore(const Pin *pin)
{
  auto itr = pin_exceptions_.find(pin);
  if (itr != pin_exceptions_.end()) {
    for (ExceptionPath *exception : itr->second) {
      ExceptionFrom *from = exception->from();
      if (from)
	from->deletePinBefore(pin, network_);
      ExceptionTo *to = exception->to();
      if (to)
	to->deletePinBefore(pin, network_);
      ExceptionPt *first_pt = exception->firstPt();
      ExceptionThruSeq *thrus = exception->thrus();
      if (thrus) {
        for (ExceptionThru *thru : *exception->thrus()) {
	  thru->deletePinBefore(pin, network_);
          if (thru == first_pt)
            recordExceptionEdges(exception, thru->edges(),
                                 first_thru_edge_exceptions_);
        }
      }
    }
    first_from_pin_exceptions_.erase(pin);
    first_thru_pin_exceptions_.erase(pin);
    first_to_pin_exceptions_.erase(pin);
    pin_exceptions_.erase(pin);
  }
  drvr_pin_wire_cap_map_.erase(pin);
}

void
Sdc::clkHpinDisablesChanged(const Pin *pin)
{
  if (isLeafPinClock(pin))
    clkHpinDisablesInvalid();
}

////////////////////////////////////////////////////////////////

// Find the leaf load pins corresponding to pin.
// If the pin is hierarchical, the leaf pins are:
//   hierarchical  input - load pins  inside the hierarchical instance
//   hierarchical output - load pins outside the hierarchical instance
void
findLeafLoadPins(const Pin *pin,
                 const Network *network,
                 PinSet *leaf_pins)
{
  if (network->isHierarchical(pin)) {
    PortDirection *dir = network->direction(pin);
    bool is_input = dir->isAnyInput();
    bool is_output = dir->isAnyOutput();
    const Instance *hinst = network->instance(pin);
    PinConnectedPinIterator *pin_iter = network->connectedPinIterator(pin);
    while (pin_iter->hasNext()) {
      const Pin *pin1 = pin_iter->next();
      bool is_inside = network->isInside(pin1, hinst);
      if (((is_input && is_inside)
           || (is_output && !is_inside))
          && network->isLoad(pin1))
        leaf_pins->insert(pin1);
    }
    delete pin_iter;
  }
  else
    leaf_pins->insert(pin);
}

// Find the leaf driver pins corresponding to pin.
// If the pin is hierarchical, the leaf pins are:
//   hierarchical  input - driver pins outside the hierarchical instance
//   hierarchical output - driver pins  inside the hierarchical instance
void
findLeafDriverPins(const Pin *pin,
                   const Network *network,
                   PinSet *leaf_pins)
{
  if (network->isHierarchical(pin)) {
    PortDirection *dir = network->direction(pin);
    bool is_input = dir->isAnyInput();
    bool is_output = dir->isAnyOutput();
    const Instance *hinst = network->instance(pin);
    PinConnectedPinIterator *pin_iter = network->connectedPinIterator(pin);
    while (pin_iter->hasNext()) {
      const Pin *pin1 = pin_iter->next();
      bool is_inside = network->isInside(pin1, hinst);
      if (((is_input && !is_inside)
           || (is_output && is_inside))
          && network->isDriver(pin1))
        leaf_pins->insert(pin1);
    }
    delete pin_iter;
  }
  else
    leaf_pins->insert(pin);
}

////////////////////////////////////////////////////////////////

bool
NetWireCaps::subtractPinCap(const MinMax *min_max)
{
  return subtract_pin_cap_[min_max->index()];
}

void
NetWireCaps::setSubtractPinCap(bool subtrace_pin_cap,
                               const MinMax *min_max)
{
  subtract_pin_cap_[min_max->index()] = subtrace_pin_cap;
}

} // namespace sta
