Fixing issue #1782

This patch establishes "nan", "inf" and "-inf" as
valid values for tl::Variant, so corresponding
PCell parameters can be serialized and are
properly managed.
This commit is contained in:
Matthias Koefferlein 2024-07-13 18:06:56 +02:00
parent f0b15e1049
commit 0df6339f4e
6 changed files with 353 additions and 5 deletions

View File

@ -361,6 +361,10 @@ module RBA
def param(name, type, description, args = {})
if ! args.is_a?(Hash)
raise("Too many positional arguments for 'param' (3 expected) - use named arguments for default value etc.")
end
if name !~ /^[_A-Za-z]\w*$/
raise "Invalid parameter name #{name} (needs to be a word)"
end

View File

@ -226,9 +226,28 @@ bool skip_newline (const char *&cp)
}
}
// -------------------------------------------------------------------------
// Utility: case-insensitive compare of the first characters
static bool local_compare (const char *s1, const char *s2)
{
while (*s1 && *s2) {
uint32_t c1 = utf32_downcase (utf32_from_utf8 (s1));
uint32_t c2 = utf32_downcase (utf32_from_utf8 (s2));
if (c1 != c2) {
return false;
}
}
return true;
}
// -------------------------------------------------------------------------
// Utility: a strtod version that is independent of the locale
static std::string inf_string = "inf";
static std::string ninf_string = "-inf";
static std::string nan_string = "nan";
static std::string micron_format ("%.5f");
static std::string dbu_format ("%.2f");
@ -244,12 +263,24 @@ void set_db_resolution (unsigned int ndigits)
std::string micron_to_string (double d)
{
return tl::sprintf (micron_format.c_str (), d);
if (std::isnan (d)) {
return nan_string;
} else if (std::isinf (d)) {
return d < 0 ? ninf_string : inf_string;
} else {
return tl::sprintf (micron_format.c_str (), d);
}
}
std::string db_to_string (double d)
{
return tl::sprintf (dbu_format.c_str (), d);
if (std::isnan (d)) {
return nan_string;
} else if (std::isinf (d)) {
return d < 0 ? ninf_string : inf_string;
} else {
return tl::sprintf (dbu_format.c_str (), d);
}
}
std::string to_upper_case (const std::string &s)
@ -317,6 +348,18 @@ static double local_strtod (const char *cp, const char *&cp_new)
{
const char *cp0 = cp;
// special numerical values
if (local_compare (cp, nan_string.c_str ())) {
cp_new = cp + nan_string.size ();
return NAN;
} else if (local_compare (cp, inf_string.c_str ())) {
cp_new = cp + inf_string.size ();
return INFINITY;
} else if (local_compare (cp, ninf_string.c_str ())) {
cp_new = cp + ninf_string.size ();
return -INFINITY;
}
// Extract sign
double s = 1.0;
if (*cp == '-') {
@ -376,6 +419,12 @@ static double local_strtod (const char *cp, const char *&cp_new)
std::string
to_string (double d, int prec)
{
if (std::isnan (d)) {
return nan_string;
} else if (std::isinf (d)) {
return d < 0 ? ninf_string : inf_string;
}
// For small values less than 1e-(prec) simply return "0" to avoid ugly values like "1.2321716e-14".
if (fabs (d) < pow (10.0, -prec)) {
return "0";
@ -393,6 +442,12 @@ to_string (double d, int prec)
std::string
to_string (float d, int prec)
{
if (std::isnan (d)) {
return nan_string;
} else if (std::isinf (d)) {
return d < 0 ? ninf_string : inf_string;
}
// For small values less than 1e-(prec) simply return "0" to avoid ugly values like "1.2321716e-14".
if (fabs (d) < pow (10.0, -prec)) {
return "0";
@ -813,6 +868,7 @@ from_string_numeric (const std::string &s, double &v, bool eval)
if (! *cp) {
throw tl::Exception (tl::to_string (tr ("Got empty string where a real number was expected")));
}
const char *cp_end = cp;
v = local_strtod (cp, cp_end);
while (safe_isspace (*cp_end)) {

View File

@ -1057,15 +1057,38 @@ normalized_type (Variant::type type1, Variant::type type2)
static const double epsilon = 1e-13;
// NOTE: in order to be able to use Variant for std::map or std::set
// keys we have to establish a weak order. This means we need to
// consider NAN and INFINITY too.
static int numeric_class (double x)
{
if (std::isnan (x)) {
return 2;
} else {
return std::isinf (x) ? (x < 0 ? -1 : 1) : 0;
}
}
static inline bool fequal (double a, double b)
{
double avg = 0.5 * (fabs (a) + fabs (b));
return fabs (a - b) <= epsilon * avg;
if (numeric_class (a) != 0 || numeric_class (b) != 0) {
return numeric_class (a) == numeric_class (b);
} else {
double avg = 0.5 * (fabs (a) + fabs (b));
return fabs (a - b) <= epsilon * avg;
}
}
static inline bool fless (double a, double b)
{
return fequal (a, b) ? false : a < b;
if (fequal (a, b)) {
return false;
} else if (numeric_class (a) != 0 || numeric_class (b) != 0) {
return numeric_class (a) < numeric_class (b);
} else {
return a < b;
}
}
bool

View File

@ -589,3 +589,62 @@ TEST(15)
EXPECT_EQ (tl::to_upper_case ("nOrMaliI(\xc3\xa4\xc3\x84\xc3\xbc\xc3\x9c\xc3\xb6\xc3\x96\xc3\x9f-42\xc2\xb0+6\xe2\x82\xac)"), "NORMALII(\xc3\x84\xc3\x84\xc3\x9c\xc3\x9c\xc3\x96\xc3\x96\xc3\x9f-42\xc2\xb0+6\xe2\x82\xac)");
EXPECT_EQ (tl::to_lower_case ("nOrMaliI(\xc3\xa4\xc3\x84\xc3\xbc\xc3\x9c\xc3\xb6\xc3\x96\xc3\x9f-42\xc2\xb0+6\xe2\x82\xac)"), "normalii(\xc3\xa4\xc3\xa4\xc3\xbc\xc3\xbc\xc3\xb6\xc3\xb6\xc3\x9f-42\xc2\xb0+6\xe2\x82\xac)");
}
// Special numerical values
TEST(16)
{
EXPECT_EQ (tl::to_string (NAN), "nan");
EXPECT_EQ (tl::to_string (INFINITY), "inf");
EXPECT_EQ (tl::to_string (-INFINITY), "-inf");
EXPECT_EQ (tl::to_string ((float) NAN), "nan");
EXPECT_EQ (tl::to_string ((float) INFINITY), "inf");
EXPECT_EQ (tl::to_string ((float) -INFINITY), "-inf");
EXPECT_EQ (tl::micron_to_string (NAN), "nan");
EXPECT_EQ (tl::micron_to_string (INFINITY), "inf");
EXPECT_EQ (tl::micron_to_string (-INFINITY), "-inf");
EXPECT_EQ (tl::db_to_string (NAN), "nan");
EXPECT_EQ (tl::db_to_string (INFINITY), "inf");
EXPECT_EQ (tl::db_to_string (-INFINITY), "-inf");
double x = 0.0;
tl::from_string ("nan", x);
EXPECT_EQ (tl::to_string (x), "nan");
x = 0.0;
tl::from_string ("NaN", x);
EXPECT_EQ (tl::to_string (x), "nan");
x = 0.0;
tl::from_string ("inf", x);
EXPECT_EQ (tl::to_string (x), "inf");
x = 0.0;
tl::from_string ("INF", x);
EXPECT_EQ (tl::to_string (x), "inf");
x = 0.0;
tl::from_string ("-inf", x);
EXPECT_EQ (tl::to_string (x), "-inf");
x = 0.0;
tl::from_string ("-INF", x);
EXPECT_EQ (tl::to_string (x), "-inf");
std::string s;
tl::Extractor ex;
x = 0.0;
s = " inf nan\t -inf";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "inf");
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "nan");
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "-inf");
s = " Inf NaN\t -INF";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "inf");
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "nan");
EXPECT_EQ (ex.try_read (x), true);
EXPECT_EQ (tl::to_string (x), "-inf");
}

View File

@ -1090,4 +1090,110 @@ TEST(6)
EXPECT_EQ (tl::Variant (-0.1 * (1.0 + 1.1e-13)) < tl::Variant (0.1), true);
}
// special numeric values
TEST(7)
{
std::string s;
tl::Extractor ex;
tl::Variant v;
s = " ##\t 0.5";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##0.5");
s = "## nan";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##nan");
s = "## NaN";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##nan");
s = "## inf";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##inf");
s = "## Inf";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##inf");
s = "## -inf";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##-inf");
s = "## -Inf";
ex = tl::Extractor (s.c_str ());
EXPECT_EQ (ex.try_read (v), true);
EXPECT_EQ (v.to_parsable_string (), "##-inf");
v = tl::Variant ("nan");
v = tl::Variant (v.to_double ());
EXPECT_EQ (v.to_parsable_string (), "##nan");
EXPECT_EQ (v.to_string (), "nan");
v = tl::Variant ("Inf");
v = tl::Variant (v.to_double ());
EXPECT_EQ (v.to_parsable_string (), "##inf");
EXPECT_EQ (v.to_string (), "inf");
v = tl::Variant (INFINITY);
EXPECT_EQ (v.to_parsable_string (), "##inf");
EXPECT_EQ (v.to_string (), "inf");
v = tl::Variant (-INFINITY);
EXPECT_EQ (v.to_parsable_string (), "##-inf");
EXPECT_EQ (v.to_string (), "-inf");
tl::Variant vinf (INFINITY);
tl::Variant vninf (-INFINITY);
tl::Variant vnan (NAN);
tl::Variant vzero (0.0);
EXPECT_EQ (vninf == vninf, true);
EXPECT_EQ (vninf == vzero, false);
EXPECT_EQ (vninf == vinf, false);
EXPECT_EQ (vninf == vnan, false);
EXPECT_EQ (vninf < vninf, false);
EXPECT_EQ (vninf < vzero, true);
EXPECT_EQ (vninf < vinf, true);
EXPECT_EQ (vninf < vnan, true);
EXPECT_EQ (vzero == vninf, false);
EXPECT_EQ (vzero == vzero, true);
EXPECT_EQ (vzero == vinf, false);
EXPECT_EQ (vzero == vnan, false);
EXPECT_EQ (vzero < vninf, false);
EXPECT_EQ (vzero < vzero, false);
EXPECT_EQ (vzero < vinf, true);
EXPECT_EQ (vzero < vnan, true);
EXPECT_EQ (vinf == vninf, false);
EXPECT_EQ (vinf == vzero, false);
EXPECT_EQ (vinf == vinf, true);
EXPECT_EQ (vinf == vnan, false);
EXPECT_EQ (vinf < vninf, false);
EXPECT_EQ (vinf < vzero, false);
EXPECT_EQ (vinf < vinf, false);
EXPECT_EQ (vinf < vnan, true);
EXPECT_EQ (vnan == vninf, false);
EXPECT_EQ (vnan == vzero, false);
EXPECT_EQ (vnan == vinf, false);
EXPECT_EQ (vnan == vnan, true);
EXPECT_EQ (vnan < vninf, false);
EXPECT_EQ (vnan < vzero, false);
EXPECT_EQ (vnan < vinf, false);
EXPECT_EQ (vnan < vnan, false);
}
}

View File

@ -674,6 +674,106 @@ class DBPCell_TestClass < TestBase
end
# issue #1782
class Circle1782 < RBA::PCellDeclarationHelper
def initialize
super()
param("l", TypeLayer, "Layer", :default => RBA::LayerInfo::new(1, 0))
param("r", TypeDouble, "Radius", :default => 1.0)
param("n", TypeInt, "Number of points", :default => 16)
end
def display_text_impl
r = self.r
if !r
r = "nil"
else
r = '%.3f' % r
end
"Circle(L=" + self.l.to_s + ",R=" + r + ")"
end
def produce_impl
r = self.r
if self.r.to_s == 'NaN'
r = 2.0
end
da = Math::PI * 2 / self.n
pts = self.n.times.collect do |i|
RBA::DPoint::new(r * Math::cos(i * da), r * Math::sin(i * da))
end
self.cell.shapes(self.l_layer).insert(RBA::DPolygon::new(pts))
end
end
class CircleLib1782 < RBA::Library
def initialize(name)
self.description = "Circle Library"
self.layout.register_pcell("Circle", Circle1782::new)
register(name)
end
def reregister_pcell
self.layout.register_pcell("Circle", Circle1782::new)
end
end
def test_10
lib = CircleLib1782::new("CircleLib")
ly = RBA::Layout::new
top = ly.create_cell("TOP")
names = []
2.times do |pass|
5.times do |i|
5.times do |j|
if (i + j) % 2 == 0
r = Float::NAN
else
r = (i + j) * 0.5
end
n = i * 5 + j
c = ly.create_cell("Circle", "CircleLib", { "l" => RBA::LayerInfo::new(1, 0), "r" => r, "n" => n })
if pass == 0
names << c.name
else
# triggered bug #1782 - basically all variants are supposed to be unique, but
# the NaN spoiled the hash maps
assert_equal(names.shift, c.name)
end
top.insert(RBA::DCellInstArray::new(c, RBA::DTrans::new(i * 10.0, j * 10.0)))
end
end
end
tmp = File::join($ut_testtmp, "tmp.gds")
ly.write(tmp)
# this should not throw an internal error
ly._destroy
# we should be able to read the Layout back
ly = RBA::Layout::new
ly.read(tmp)
assert_equal(ly.top_cell.name, "TOP")
assert_equal(ly.cells, 26)
ly._destroy
lib._destroy
end
end
load("test_epilogue.rb")