test: Apply review feedback - part1

Signed-off-by: Jaehyun Kim <[email protected]>
This commit is contained in:
Jaehyun Kim
2026-02-19 23:30:23 +09:00
parent fdb24d8132
commit 6799b1909a
585 changed files with 5549 additions and 99794 deletions
+746
View File
@@ -4138,4 +4138,750 @@ TEST_F(DesignDcalcTest, LevelChangedBefore) {
}
}
////////////////////////////////////////////////////////////////
// NangateDcalcTest - Loads Nangate45 + dcalc_test1.v (BUF->INV->DFF chain)
class NangateDcalcTest : public ::testing::Test {
protected:
void SetUp() override {
interp_ = Tcl_CreateInterp();
initSta();
sta_ = new Sta;
Sta::setSta(sta_);
sta_->makeComponents();
ReportTcl *report = dynamic_cast<ReportTcl*>(sta_->report());
if (report)
report->setTclInterp(interp_);
registerDelayCalcs();
Corner *corner = sta_->cmdCorner();
const MinMaxAll *min_max = MinMaxAll::all();
LibertyLibrary *lib = sta_->readLiberty(
"test/nangate45/Nangate45_typ.lib", corner, min_max, false);
ASSERT_NE(lib, nullptr);
bool ok = sta_->readVerilog("dcalc/test/dcalc_test1.v");
ASSERT_TRUE(ok);
ok = sta_->linkDesign("dcalc_test1", true);
ASSERT_TRUE(ok);
// Create clock and set constraints
Network *network = sta_->network();
Instance *top = network->topInstance();
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(5.0f);
sta_->makeClock("clk", clk_pins, false, 10.0f, waveform, nullptr);
// Set input/output delay constraints to create constrained timing paths
Clock *clk = sta_->sdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Pin *in1_pin = network->findPin(top, "in1");
ASSERT_NE(in1_pin, nullptr);
sta_->setInputDelay(in1_pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
Pin *out1_pin = network->findPin(top, "out1");
ASSERT_NE(out1_pin, nullptr);
sta_->setOutputDelay(out1_pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
design_loaded_ = true;
}
void TearDown() override {
deleteDelayCalcs();
deleteAllMemory();
sta_ = nullptr;
if (interp_) Tcl_DeleteInterp(interp_);
interp_ = nullptr;
}
Sta *sta_;
Tcl_Interp *interp_;
bool design_loaded_ = false;
};
// Run updateTiming with each calculator, verify all complete without crash
// and graph has delays.
TEST_F(NangateDcalcTest, TimingAllCalcsNangate) {
EXPECT_TRUE(design_loaded_);
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
"dmp_ceff_two_pole", "ccs_ceff"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
// Set various loads on output, run dmp_ceff_elmore for each, verify slack changes.
TEST_F(NangateDcalcTest, DmpExtremeLoads) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *out_port = network->findPort(top_cell, "out1");
ASSERT_NE(out_port, nullptr);
Corner *corner = sta_->cmdCorner();
float loads[] = {0.00001f, 0.1f, 1.0f, 5.0f, 10.0f};
Slack prev_slack = 0.0f;
bool first = true;
for (float load : loads) {
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), load);
sta_->updateTiming(true);
Slack slack = sta_->worstSlack(MinMax::max());
if (!first) {
// With increasing load, slack should generally decrease (become worse)
// but we just verify it's a valid number and changes
EXPECT_TRUE(slack != prev_slack || load == loads[0]);
}
prev_slack = slack;
first = false;
}
}
// Set various input transitions via setInputSlew, verify timing completes.
TEST_F(NangateDcalcTest, DmpExtremeSlews) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *in_port = network->findPort(top_cell, "in1");
ASSERT_NE(in_port, nullptr);
float slews[] = {0.0001f, 0.1f, 5.0f, 10.0f};
for (float slew : slews) {
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), slew);
sta_->updateTiming(true);
SUCCEED();
}
}
// Large load + fast slew, tiny load + slow slew combinations.
TEST_F(NangateDcalcTest, DmpCombinedExtremes) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *out_port = network->findPort(top_cell, "out1");
const Port *in_port = network->findPort(top_cell, "in1");
ASSERT_NE(out_port, nullptr);
ASSERT_NE(in_port, nullptr);
Corner *corner = sta_->cmdCorner();
// Large load + fast slew
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), 10.0f);
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.0001f);
sta_->updateTiming(true);
Slack slack1 = sta_->worstSlack(MinMax::max());
// Tiny load + slow slew
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), 0.00001f);
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 10.0f);
sta_->updateTiming(true);
Slack slack2 = sta_->worstSlack(MinMax::max());
// Just verify both complete and produce different slacks
EXPECT_NE(slack1, slack2);
}
// Same as DmpExtremeLoads but with dmp_ceff_two_pole.
TEST_F(NangateDcalcTest, TwoPoleExtremeLoads) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_two_pole");
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *out_port = network->findPort(top_cell, "out1");
ASSERT_NE(out_port, nullptr);
Corner *corner = sta_->cmdCorner();
float loads[] = {0.00001f, 0.1f, 1.0f, 5.0f, 10.0f};
for (float load : loads) {
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), load);
sta_->updateTiming(true);
SUCCEED();
}
}
// Switch calculator from dmp_ceff_elmore->lumped_cap->unit->dmp_ceff_two_pole,
// verify timing works at each switch.
TEST_F(NangateDcalcTest, CalcSwitchingIncremental) {
EXPECT_TRUE(design_loaded_);
const char *calcs[] = {"dmp_ceff_elmore", "lumped_cap", "unit",
"dmp_ceff_two_pole"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
// Set ccs_ceff (falls back to table-based for NLDM), verify timing works.
TEST_F(NangateDcalcTest, CcsWithNldmFallback) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("ccs_ceff");
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
Slack slack = sta_->worstSlack(MinMax::max());
// CCS with NLDM fallback should still produce valid timing
(void)slack;
SUCCEED();
}
// Set ccs_ceff, change load, verify incremental timing.
TEST_F(NangateDcalcTest, CcsIncrementalLoadChange) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("ccs_ceff");
sta_->updateTiming(true);
Slack slack1 = sta_->worstSlack(MinMax::max());
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *out_port = network->findPort(top_cell, "out1");
ASSERT_NE(out_port, nullptr);
Corner *corner = sta_->cmdCorner();
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), 5.0f);
sta_->updateTiming(false);
Slack slack2 = sta_->worstSlack(MinMax::max());
// With large load, slack should change
EXPECT_NE(slack1, slack2);
}
////////////////////////////////////////////////////////////////
// MultiDriverDcalcTest - Loads Nangate45 + dcalc_multidriver_test.v
class MultiDriverDcalcTest : public ::testing::Test {
protected:
void SetUp() override {
interp_ = Tcl_CreateInterp();
initSta();
sta_ = new Sta;
Sta::setSta(sta_);
sta_->makeComponents();
ReportTcl *report = dynamic_cast<ReportTcl*>(sta_->report());
if (report)
report->setTclInterp(interp_);
registerDelayCalcs();
Corner *corner = sta_->cmdCorner();
const MinMaxAll *min_max = MinMaxAll::all();
LibertyLibrary *lib = sta_->readLiberty(
"test/nangate45/Nangate45_typ.lib", corner, min_max, false);
ASSERT_NE(lib, nullptr);
bool ok = sta_->readVerilog("dcalc/test/dcalc_multidriver_test.v");
ASSERT_TRUE(ok);
ok = sta_->linkDesign("dcalc_multidriver_test", true);
ASSERT_TRUE(ok);
// Create clock
Network *network = sta_->network();
Instance *top = network->topInstance();
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(5.0f);
sta_->makeClock("clk", clk_pins, false, 10.0f, waveform, nullptr);
Clock *clk = sta_->sdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
// Set input delays on in1-in4, sel
Cell *top_cell = network->cell(top);
const char *input_ports[] = {"in1", "in2", "in3", "in4", "sel"};
for (const char *pname : input_ports) {
const Port *port = network->findPort(top_cell, pname);
if (port) {
sta_->setInputSlew(port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.1f);
// Also set SDC input delay to constrain the path
Pin *pin = network->findPin(top, pname);
if (pin) {
sta_->setInputDelay(pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
}
}
}
// Set output loads and output delays on out1-out3
const char *output_ports[] = {"out1", "out2", "out3"};
for (const char *pname : output_ports) {
const Port *port = network->findPort(top_cell, pname);
if (port) {
sta_->setPortExtPinCap(port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), 0.01f);
Pin *pin = network->findPin(top, pname);
if (pin) {
sta_->setOutputDelay(pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
}
}
}
design_loaded_ = true;
}
void TearDown() override {
deleteDelayCalcs();
deleteAllMemory();
sta_ = nullptr;
if (interp_) Tcl_DeleteInterp(interp_);
interp_ = nullptr;
}
Sta *sta_;
Tcl_Interp *interp_;
bool design_loaded_ = false;
};
// updateTiming, query paths from each input to each output, verify graph has paths.
TEST_F(MultiDriverDcalcTest, AllPathQueries) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 10);
Network *network = sta_->network();
Instance *top = network->topInstance();
// Verify output pins have vertices
const char *out_names[] = {"out1", "out2", "out3"};
for (const char *name : out_names) {
Pin *pin = network->findPin(top, name);
ASSERT_NE(pin, nullptr);
Vertex *v = graph->pinDrvrVertex(pin);
EXPECT_NE(v, nullptr);
}
}
// Sweep loads 0.001->0.1 on out1, verify delays change monotonically.
TEST_F(MultiDriverDcalcTest, DmpCeffLoadSweep) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *out_port = network->findPort(top_cell, "out1");
ASSERT_NE(out_port, nullptr);
Corner *corner = sta_->cmdCorner();
float loads[] = {0.001f, 0.005f, 0.01f, 0.05f, 0.1f};
Slack prev_slack = 1e30f; // Start with large positive value
for (float load : loads) {
sta_->setPortExtPinCap(out_port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), load);
sta_->updateTiming(true);
Slack slack = sta_->worstSlack(MinMax::max());
// With increasing load, slack should decrease (more negative = worse)
EXPECT_LE(slack, prev_slack + 1e-6f);
prev_slack = slack;
}
}
// Set large tolerance (0.5), change slew, verify timing completes.
TEST_F(MultiDriverDcalcTest, IncrementalToleranceLarge) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->setIncrementalDelayTolerance(0.5f);
sta_->updateTiming(true);
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *in_port = network->findPort(top_cell, "in1");
ASSERT_NE(in_port, nullptr);
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.5f);
sta_->updateTiming(false);
SUCCEED();
}
// Set small tolerance (0.001), change slew, verify timing recomputes.
TEST_F(MultiDriverDcalcTest, IncrementalToleranceSmall) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->setIncrementalDelayTolerance(0.001f);
sta_->updateTiming(true);
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *in_port = network->findPort(top_cell, "in1");
ASSERT_NE(in_port, nullptr);
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 0.5f);
sta_->updateTiming(false);
SUCCEED();
}
// Set loads on multiple outputs, verify incremental update works.
TEST_F(MultiDriverDcalcTest, IncrementalLoadChanges) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
Corner *corner = sta_->cmdCorner();
const char *output_ports[] = {"out1", "out2", "out3"};
for (const char *pname : output_ports) {
const Port *port = network->findPort(top_cell, pname);
ASSERT_NE(port, nullptr);
sta_->setPortExtPinCap(port, RiseFallBoth::riseFall(),
corner, MinMaxAll::all(), 1.0f);
}
sta_->updateTiming(false);
Slack slack = sta_->worstSlack(MinMax::max());
(void)slack;
SUCCEED();
}
// Change clock period, verify timing updates.
TEST_F(MultiDriverDcalcTest, IncrementalClockPeriodChange) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Slack slack1 = sta_->worstSlack(MinMax::max());
// Create new clock with different period
Network *network = sta_->network();
Instance *top = network->topInstance();
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(1.0f);
sta_->makeClock("clk", clk_pins, false, 2.0f, waveform, nullptr);
sta_->updateTiming(true);
Slack slack2 = sta_->worstSlack(MinMax::max());
// Tighter clock => smaller (worse) slack
EXPECT_NE(slack1, slack2);
}
// Replace buf1 with BUF_X4, verify timing completes, replace back.
TEST_F(MultiDriverDcalcTest, ReplaceCellIncremental) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Network *network = sta_->network();
Instance *top = network->topInstance();
Instance *buf1 = network->findChild(top, "buf1");
ASSERT_NE(buf1, nullptr);
LibertyCell *buf_x4 = network->findLibertyCell("BUF_X4");
ASSERT_NE(buf_x4, nullptr);
LibertyCell *buf_x1 = network->findLibertyCell("BUF_X1");
ASSERT_NE(buf_x1, nullptr);
// Check vertex delay on buf1 output before replacement
Graph *graph = sta_->graph();
Pin *buf1_z = network->findPin(buf1, "Z");
ASSERT_NE(buf1_z, nullptr);
Vertex *v1 = graph->pinDrvrVertex(buf1_z);
ASSERT_NE(v1, nullptr);
sta_->replaceCell(buf1, buf_x4);
sta_->updateTiming(true);
// Verify timing completes after replacement
graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
// Replace back to original
sta_->replaceCell(buf1, buf_x1);
sta_->updateTiming(true);
graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
SUCCEED();
}
// Switch through all 5 calculators, verify timing at each.
TEST_F(MultiDriverDcalcTest, CalcSwitchAllEngines) {
EXPECT_TRUE(design_loaded_);
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
"dmp_ceff_two_pole", "ccs_ceff"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
// Call findDelays() directly, invalidate, call again.
TEST_F(MultiDriverDcalcTest, FindDelaysExplicit) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->findDelays();
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
// Change something and call findDelays again
Network *network = sta_->network();
Instance *top = network->topInstance();
Cell *top_cell = network->cell(top);
const Port *in_port = network->findPort(top_cell, "in1");
ASSERT_NE(in_port, nullptr);
sta_->setInputSlew(in_port, RiseFallBoth::riseFall(),
MinMaxAll::all(), 1.0f);
sta_->findDelays();
SUCCEED();
}
////////////////////////////////////////////////////////////////
// MultiCornerDcalcTest - Loads Nangate45 fast/slow + dcalc_test1.v
class MultiCornerDcalcTest : public ::testing::Test {
protected:
void SetUp() override {
interp_ = Tcl_CreateInterp();
initSta();
sta_ = new Sta;
Sta::setSta(sta_);
sta_->makeComponents();
ReportTcl *report = dynamic_cast<ReportTcl*>(sta_->report());
if (report)
report->setTclInterp(interp_);
registerDelayCalcs();
// Define corners
StringSet corner_names;
corner_names.insert("fast");
corner_names.insert("slow");
sta_->makeCorners(&corner_names);
Corner *fast_corner = sta_->findCorner("fast");
Corner *slow_corner = sta_->findCorner("slow");
ASSERT_NE(fast_corner, nullptr);
ASSERT_NE(slow_corner, nullptr);
const MinMaxAll *min_max = MinMaxAll::all();
LibertyLibrary *fast_lib = sta_->readLiberty(
"test/nangate45/Nangate45_fast.lib", fast_corner, min_max, false);
ASSERT_NE(fast_lib, nullptr);
LibertyLibrary *slow_lib = sta_->readLiberty(
"test/nangate45/Nangate45_slow.lib", slow_corner, min_max, false);
ASSERT_NE(slow_lib, nullptr);
bool ok = sta_->readVerilog("dcalc/test/dcalc_test1.v");
ASSERT_TRUE(ok);
ok = sta_->linkDesign("dcalc_test1", true);
ASSERT_TRUE(ok);
// Create clock
Network *network = sta_->network();
Instance *top = network->topInstance();
Pin *clk_pin = network->findPin(top, "clk");
ASSERT_NE(clk_pin, nullptr);
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk_pin);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(5.0f);
sta_->makeClock("clk", clk_pins, false, 10.0f, waveform, nullptr);
// Set input/output delay constraints to create constrained timing paths
Clock *clk = sta_->sdc()->findClock("clk");
ASSERT_NE(clk, nullptr);
Pin *in1_pin = network->findPin(top, "in1");
ASSERT_NE(in1_pin, nullptr);
sta_->setInputDelay(in1_pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
Pin *out1_pin = network->findPin(top, "out1");
ASSERT_NE(out1_pin, nullptr);
sta_->setOutputDelay(out1_pin, RiseFallBoth::riseFall(), clk,
RiseFall::rise(), nullptr, false, false,
MinMaxAll::all(), false, 0.0f);
design_loaded_ = true;
}
void TearDown() override {
deleteDelayCalcs();
deleteAllMemory();
sta_ = nullptr;
if (interp_) Tcl_DeleteInterp(interp_);
interp_ = nullptr;
}
Sta *sta_;
Tcl_Interp *interp_;
bool design_loaded_ = false;
};
// Verify timing with both corners produces valid results and
// that the slow corner does not have better slack than the fast corner.
TEST_F(MultiCornerDcalcTest, TimingDiffersPerCorner) {
EXPECT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Corner *fast_corner = sta_->findCorner("fast");
Corner *slow_corner = sta_->findCorner("slow");
ASSERT_NE(fast_corner, nullptr);
ASSERT_NE(slow_corner, nullptr);
Slack fast_slack, slow_slack;
Vertex *fast_vertex, *slow_vertex;
sta_->worstSlack(fast_corner, MinMax::max(), fast_slack, fast_vertex);
sta_->worstSlack(slow_corner, MinMax::max(), slow_slack, slow_vertex);
// Both corners should produce valid slack (not infinity)
EXPECT_LT(fast_slack, 1e29f);
EXPECT_LT(slow_slack, 1e29f);
// Fast corner should have slack >= slow corner (better or equal)
EXPECT_GE(fast_slack, slow_slack);
}
// Run each calculator with multi-corner, verify completes.
TEST_F(MultiCornerDcalcTest, AllCalcsMultiCorner) {
EXPECT_TRUE(design_loaded_);
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
"dmp_ceff_two_pole", "ccs_ceff"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
////////////////////////////////////////////////////////////////
// Additional DesignDcalcTest tests for SPEF-based scenarios
// Run all delay calculators with SPEF loaded.
TEST_F(DesignDcalcTest, TimingAllCalcsWithSpef) {
ASSERT_TRUE(design_loaded_);
const char *calcs[] = {"unit", "lumped_cap", "dmp_ceff_elmore",
"dmp_ceff_two_pole", "arnoldi", "ccs_ceff", "prima"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
// Set prima reduce order 1,2,3,4,5, verify each completes.
TEST_F(DesignDcalcTest, PrimaReduceOrderVariation) {
ASSERT_TRUE(design_loaded_);
sta_->setArcDelayCalc("prima");
ArcDelayCalc *calc = sta_->arcDelayCalc();
ASSERT_NE(calc, nullptr);
PrimaDelayCalc *prima = dynamic_cast<PrimaDelayCalc*>(calc);
ASSERT_NE(prima, nullptr);
size_t orders[] = {1, 2, 3, 4, 5};
for (size_t order : orders) {
prima->setPrimaReduceOrder(order);
sta_->updateTiming(true);
SUCCEED();
}
}
// Change load, slew, clock period with SPEF, verify updates.
TEST_F(DesignDcalcTest, IncrementalWithSpef) {
ASSERT_TRUE(design_loaded_);
sta_->setArcDelayCalc("dmp_ceff_elmore");
sta_->updateTiming(true);
Slack slack1 = sta_->worstSlack(MinMax::max());
// Change clock period
Network *network = sta_->network();
Instance *top = network->topInstance();
Pin *clk1 = network->findPin(top, "clk1");
Pin *clk2 = network->findPin(top, "clk2");
Pin *clk3 = network->findPin(top, "clk3");
PinSet *clk_pins = new PinSet(network);
clk_pins->insert(clk1);
clk_pins->insert(clk2);
clk_pins->insert(clk3);
FloatSeq *waveform = new FloatSeq;
waveform->push_back(0.0f);
waveform->push_back(50.0f);
sta_->makeClock("clk", clk_pins, false, 100.0f, waveform, nullptr);
sta_->updateTiming(true);
Slack slack2 = sta_->worstSlack(MinMax::max());
// Tighter clock => different slack
EXPECT_NE(slack1, slack2);
}
// Rapidly switch between all calcs with SPEF loaded.
TEST_F(DesignDcalcTest, RapidCalcSwitchingSpef) {
ASSERT_TRUE(design_loaded_);
const char *calcs[] = {"dmp_ceff_elmore", "lumped_cap", "unit",
"dmp_ceff_two_pole", "arnoldi", "ccs_ceff",
"prima", "dmp_ceff_elmore", "ccs_ceff"};
for (const char *name : calcs) {
sta_->setArcDelayCalc(name);
sta_->updateTiming(true);
Graph *graph = sta_->graph();
ASSERT_NE(graph, nullptr);
EXPECT_GT(graph->vertexCount(), 0);
}
}
} // namespace sta
+572
View File
@@ -7,6 +7,10 @@ namespace sta {
class FindRootTest : public ::testing::Test {};
////////////////////////////////////////////////////////////////
// Original 7 tests
////////////////////////////////////////////////////////////////
// Test finding root of f(x) = x^2 - 4 (root at x=2)
TEST_F(FindRootTest, QuadraticPositiveRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
@@ -92,4 +96,572 @@ TEST_F(FindRootTest, WithPrecomputedY) {
EXPECT_NEAR(root, 3.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Tolerance edge cases
////////////////////////////////////////////////////////////////
// Very tight tolerance: 1e-15 (near machine epsilon)
TEST_F(FindRootTest, VeryTightTolerance) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 5.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, 3.0, 7.0, 1e-15, 500, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 5.0, 1e-13);
}
// Very loose tolerance: 1e-1
TEST_F(FindRootTest, LooseTolerance) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 25.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 3.0, 7.0, 1e-1, 100, fail);
EXPECT_FALSE(fail);
// With 10% relative tolerance, result should still be in the right ballpark
EXPECT_NEAR(root, 5.0, 0.6);
}
// Zero tolerance: convergence check becomes abs(dx) <= 0, which is only
// satisfied when dx is exactly 0. Likely hits max_iter and fails.
TEST_F(FindRootTest, ZeroTolerance) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 3.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, 1.0, 5.0, 0.0, 100, fail);
// May or may not converge -- for a linear function Newton converges in 1 step
// so dx can be exactly 0. Accept either outcome.
if (!fail) {
EXPECT_NEAR(root, 3.0, 1e-10);
}
}
////////////////////////////////////////////////////////////////
// Iteration limit edge cases
////////////////////////////////////////////////////////////////
// Only 1 iteration allowed
TEST_F(FindRootTest, OneIteration) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 4.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 1.0, 3.0, 1e-10, 1, fail);
// With only 1 iteration, a quadratic likely won't converge to tight tol
// The algorithm may or may not fail depending on initial bisection step
(void)root; // just ensure no crash
}
// Two iterations
TEST_F(FindRootTest, TwoIterations) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 7.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, 5.0, 9.0, 1e-10, 2, fail);
// Linear function: Newton should converge very fast
// After the initial midpoint (7.0), Newton step should nail it
if (!fail) {
EXPECT_NEAR(root, 7.0, 1e-6);
}
}
// Zero max iterations: the for-loop body never executes, so fail is set to true
TEST_F(FindRootTest, ZeroMaxIterations) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 1.0;
dy = 1.0;
};
bool fail = false;
findRoot(func, 0.0, 2.0, 1e-10, 0, fail);
EXPECT_TRUE(fail);
}
// Large max_iter (should still converge quickly and not hang)
TEST_F(FindRootTest, LargeMaxIter) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 16.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 1.0, 10.0, 1e-12, 10000, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 4.0, 1e-10);
}
////////////////////////////////////////////////////////////////
// Special function types
////////////////////////////////////////////////////////////////
// Cubic: f(x) = x^3 - 8 (root at x=2)
TEST_F(FindRootTest, CubicRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x * x - 8.0;
dy = 3.0 * x * x;
};
bool fail = false;
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-8);
}
// Quartic: f(x) = x^4 - 16 (root at x=2)
TEST_F(FindRootTest, QuarticRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x * x * x - 16.0;
dy = 4.0 * x * x * x;
};
bool fail = false;
double root = findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-8);
}
// Exponential: f(x) = e^x - 10 (root at x=ln(10))
TEST_F(FindRootTest, ExponentialRoot2) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = exp(x) - 10.0;
dy = exp(x);
};
bool fail = false;
double root = findRoot(func, 1.0, 4.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, log(10.0), 1e-8);
}
// Square root function: f(x) = sqrt(x) - 3, root at x=9
// Derivative: 1/(2*sqrt(x))
TEST_F(FindRootTest, SqrtFunctionRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = sqrt(x) - 3.0;
dy = 0.5 / sqrt(x);
};
bool fail = false;
double root = findRoot(func, 1.0, 20.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 9.0, 1e-6);
}
////////////////////////////////////////////////////////////////
// Near-zero roots
////////////////////////////////////////////////////////////////
// f(x) = x - 1e-10 (root very close to zero)
// Note: convergence check is abs(dx) <= x_tol * abs(root).
// When root is near zero, the relative tolerance is very tight.
// This may require many iterations or not converge.
TEST_F(FindRootTest, NearZeroRootLinear) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 1e-10;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, -1.0, 1.0, 1e-6, 200, fail);
// Newton on a linear function converges in 1-2 steps regardless of root location
if (!fail) {
EXPECT_NEAR(root, 1e-10, 1e-6);
}
}
// f(x) = x (root exactly at zero)
// Convergence test: abs(dx) <= x_tol * abs(root) = x_tol * 0 = 0
// Will likely hit max_iter because relative tolerance at root=0 requires dx=0 exactly
TEST_F(FindRootTest, RootExactlyAtZero) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, -1.0, 1.0, 1e-10, 200, fail);
// Even if fail is true, root should be very close to 0
EXPECT_NEAR(root, 0.0, 1e-6);
}
////////////////////////////////////////////////////////////////
// Negative domain
////////////////////////////////////////////////////////////////
// Root in deeply negative domain: f(x) = x + 100, root at x=-100
TEST_F(FindRootTest, NegativeDomainRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x + 100.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, -200.0, 0.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, -100.0, 1e-6);
}
// Both bracket endpoints negative: f(x) = x^2 - 1, root at x=-1
TEST_F(FindRootTest, NegativeBracketRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 1.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, -1.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Trigonometric functions
////////////////////////////////////////////////////////////////
// sin(x) root at x=0 (bracket [-1, 1])
TEST_F(FindRootTest, SinRootAtZero) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = sin(x);
dy = cos(x);
};
bool fail = false;
double root = findRoot(func, -1.0, 1.0, 1e-10, 100, fail);
// Root at 0 has the relative-tolerance issue, but Newton converges fast for sin
EXPECT_NEAR(root, 0.0, 1e-4);
}
// sin(x) root at x=2*pi (bracket [5.5, 7.0])
TEST_F(FindRootTest, SinRootAt2Pi) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = sin(x);
dy = cos(x);
};
bool fail = false;
double root = findRoot(func, 5.5, 7.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0 * M_PI, 1e-6);
}
// cos(x) root at x=pi/2
TEST_F(FindRootTest, CosRootAtPiOver2) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = cos(x);
dy = -sin(x);
};
bool fail = false;
double root = findRoot(func, 1.0, 2.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, M_PI / 2.0, 1e-6);
}
////////////////////////////////////////////////////////////////
// Multiple roots nearby
////////////////////////////////////////////////////////////////
// f(x) = (x-1)(x-2) = x^2 - 3x + 2, roots at x=1 and x=2
// Bracket [0.5, 1.5] should find x=1
TEST_F(FindRootTest, MultipleRootsFindFirst) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = (x - 1.0) * (x - 2.0);
dy = 2.0 * x - 3.0;
};
bool fail = false;
double root = findRoot(func, 0.5, 1.5, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 1.0, 1e-8);
}
// Same function, bracket [1.5, 2.5] should find x=2
TEST_F(FindRootTest, MultipleRootsFindSecond) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = (x - 1.0) * (x - 2.0);
dy = 2.0 * x - 3.0;
};
bool fail = false;
double root = findRoot(func, 1.5, 2.5, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Discontinuous derivative (sharp corner)
////////////////////////////////////////////////////////////////
// f(x) = |x| - 1 with piecewise derivative.
// Root at x=1 (bracket [0.5, 2.0] avoids the corner at 0)
TEST_F(FindRootTest, AbsValueRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = fabs(x) - 1.0;
dy = (x >= 0.0) ? 1.0 : -1.0;
};
bool fail = false;
double root = findRoot(func, 0.5, 2.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 1.0, 1e-8);
}
// f(x) = |x| - 1, root at x=-1
TEST_F(FindRootTest, AbsValueNegativeRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = fabs(x) - 1.0;
dy = (x >= 0.0) ? 1.0 : -1.0;
};
bool fail = false;
double root = findRoot(func, -2.0, -0.5, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, -1.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Very flat function (slow convergence)
////////////////////////////////////////////////////////////////
// f(x) = (x - 3)^5 has a repeated root at x=3 where the derivative is also
// zero. Newton-Raphson divides by dy which becomes 0, producing NaN.
// The algorithm is expected to fail on this degenerate case.
TEST_F(FindRootTest, FlatFifthOrderRootFails) {
FindRootFunc func = [](double x, double &y, double &dy) {
double d = x - 3.0;
double d2 = d * d;
double d4 = d2 * d2;
y = d4 * d; // (x-3)^5
dy = 5.0 * d4; // 5*(x-3)^4
};
bool fail = false;
findRoot(func, 2.0, 4.0, 1e-6, 500, fail);
// The algorithm is expected to fail because dy -> 0 at the root
EXPECT_TRUE(fail);
}
// A function that is very flat near the root but still has nonzero derivative
// at the root: f(x) = sinh(x - 3) which is ~0 near x=3 but never has dy=0.
// sinh is flat near 0 (sinh(e) ~ e for small e) but derivative cosh(e) >= 1.
TEST_F(FindRootTest, FlatSinhRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = sinh(x - 3.0);
dy = cosh(x - 3.0);
};
bool fail = false;
double root = findRoot(func, 2.0, 4.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 3.0, 1e-6);
}
////////////////////////////////////////////////////////////////
// Very steep function (fast convergence)
////////////////////////////////////////////////////////////////
// f(x) = 1000*(x - 5), root at x=5. Very steep gradient.
TEST_F(FindRootTest, SteepLinearRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = 1000.0 * (x - 5.0);
dy = 1000.0;
};
bool fail = false;
double root = findRoot(func, 3.0, 7.0, 1e-12, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 5.0, 1e-10);
}
// f(x) = 1e6 * (x - 2), very steep
TEST_F(FindRootTest, VerySteepLinearRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = 1e6 * (x - 2.0);
dy = 1e6;
};
bool fail = false;
double root = findRoot(func, 1.0, 3.0, 1e-14, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-12);
}
////////////////////////////////////////////////////////////////
// Large bracket
////////////////////////////////////////////////////////////////
// f(x) = x - 42, bracket [-1000, 1000]
TEST_F(FindRootTest, LargeBracket) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 42.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, -1000.0, 1000.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 42.0, 1e-6);
}
// Quadratic with large bracket: f(x) = x^2 - 100, root at 10
TEST_F(FindRootTest, LargeBracketQuadratic) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 100.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 1.0, 1000.0, 1e-10, 200, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 10.0, 1e-6);
}
////////////////////////////////////////////////////////////////
// Small bracket
////////////////////////////////////////////////////////////////
// f(x) = x - 1.0, bracket [0.999999, 1.000001] (very tight bracket around root)
TEST_F(FindRootTest, SmallBracket) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 1.0;
dy = 1.0;
};
bool fail = false;
double root = findRoot(func, 0.999999, 1.000001, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 1.0, 1e-6);
}
// Quadratic with very small bracket around root=2
TEST_F(FindRootTest, SmallBracketQuadratic) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 4.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 1.9999, 2.0001, 1e-12, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Both overloads tested together
////////////////////////////////////////////////////////////////
// Compare 2-arg and 4-arg overloads produce same result
TEST_F(FindRootTest, OverloadsProduceSameResult) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x * x - 27.0;
dy = 3.0 * x * x;
};
bool fail_2arg = false;
double root_2arg = findRoot(func, 2.0, 4.0, 1e-12, 100, fail_2arg);
// Pre-compute y values for 4-arg version
double y1 = 2.0 * 2.0 * 2.0 - 27.0; // 8 - 27 = -19
double y2 = 4.0 * 4.0 * 4.0 - 27.0; // 64 - 27 = 37
bool fail_4arg = false;
double root_4arg = findRoot(func, 2.0, y1, 4.0, y2, 1e-12, 100, fail_4arg);
EXPECT_FALSE(fail_2arg);
EXPECT_FALSE(fail_4arg);
EXPECT_NEAR(root_2arg, 3.0, 1e-10);
EXPECT_NEAR(root_4arg, 3.0, 1e-10);
EXPECT_NEAR(root_2arg, root_4arg, 1e-14);
}
// 4-arg overload: x1 endpoint is exact root (y1 == 0)
TEST_F(FindRootTest, FourArgX1IsRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 5.0;
dy = 1.0;
};
bool fail = false;
// y1 = 5 - 5 = 0
double root = findRoot(func, 5.0, 0.0, 8.0, 3.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_DOUBLE_EQ(root, 5.0);
}
// 4-arg overload: x2 endpoint is exact root (y2 == 0)
TEST_F(FindRootTest, FourArgX2IsRoot) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 5.0;
dy = 1.0;
};
bool fail = false;
// y2 = 5 - 5 = 0
double root = findRoot(func, 2.0, -3.0, 5.0, 0.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_DOUBLE_EQ(root, 5.0);
}
////////////////////////////////////////////////////////////////
// Same-sign y values (should fail)
////////////////////////////////////////////////////////////////
// Both endpoints positive: should fail
TEST_F(FindRootTest, BothEndpointsPositiveFails) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x + 1.0; // Always positive, no real root
dy = 2.0 * x;
};
bool fail = false;
findRoot(func, 1.0, 3.0, 1e-10, 100, fail);
EXPECT_TRUE(fail);
}
// Both endpoints negative: should fail
TEST_F(FindRootTest, BothEndpointsNegativeFails) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = -x * x - 1.0; // Always negative
dy = -2.0 * x;
};
bool fail = false;
findRoot(func, -3.0, 3.0, 1e-10, 100, fail);
EXPECT_TRUE(fail);
}
// 4-arg version: same-sign y values
TEST_F(FindRootTest, FourArgSameSignFails) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x;
dy = 2.0 * x;
};
bool fail = false;
// Both y values positive
findRoot(func, 1.0, 1.0, 2.0, 4.0, 1e-10, 100, fail);
EXPECT_TRUE(fail);
}
////////////////////////////////////////////////////////////////
// Symmetry test
////////////////////////////////////////////////////////////////
// f(x) = x^2 - 4: bracket [0, 3] finds +2, bracket [-3, 0] finds -2
TEST_F(FindRootTest, SymmetryPositiveBracket) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 4.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, 0.5, 3.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 2.0, 1e-8);
}
TEST_F(FindRootTest, SymmetryNegativeBracket) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x * x - 4.0;
dy = 2.0 * x;
};
bool fail = false;
double root = findRoot(func, -3.0, -0.5, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, -2.0, 1e-8);
}
////////////////////////////////////////////////////////////////
// Swapped bracket order (x1 > x2)
////////////////////////////////////////////////////////////////
// The algorithm should work regardless of bracket order
TEST_F(FindRootTest, SwappedBracketOrder) {
FindRootFunc func = [](double x, double &y, double &dy) {
y = x - 3.0;
dy = 1.0;
};
bool fail = false;
// x1=5 > x2=1 (reversed order)
double root = findRoot(func, 5.0, 1.0, 1e-10, 100, fail);
EXPECT_FALSE(fail);
EXPECT_NEAR(root, 3.0, 1e-8);
}
} // namespace sta