Smooth bug (#740)

* Smoothing function: provide ability to keep horizontal/vertical lines (important for cut lines)

* Introducting API compatibility macros for generic plugins.
This commit is contained in:
Matthias Köfferlein 2021-03-14 12:27:36 +01:00 committed by GitHub
parent 9e3183250f
commit 184f2bee50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 105 additions and 49 deletions

View File

@ -725,7 +725,7 @@ template DB_PUBLIC void split_polygon<> (const db::DSimplePolygon &polygon, std:
// Smoothing tools
void
smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon_contour_iterator to, std::vector <db::Point> &points, db::Coord d)
smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon_contour_iterator to, std::vector <db::Point> &points, db::Coord d, bool keep_hv)
{
points.clear ();
points.reserve (std::distance (from, to));
@ -781,7 +781,9 @@ smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon
bool can_drop = false;
if (db::Coord (p1.distance(p0)) <= d && db::sprod_sign (p2 - p1, p0 - pm1) > 0 && std::abs (db::vprod (p2 - p1, p0 - pm1)) < 0.8 * p2.distance (p1) * p0.distance (pm1)) {
if (keep_hv && (p1.x () == p0.x () || p1.y () == p0.y () || p2.x () == p1.x () || p2.y () == p1.y ())) {
// keep points which participate in either a vertical or horizontal edge
} else if (db::Coord (p1.distance(p0)) <= d && db::sprod_sign (p2 - p1, p0 - pm1) > 0 && std::abs (db::vprod (p2 - p1, p0 - pm1)) < 0.8 * p2.distance (p1) * p0.distance (pm1)) {
// jog configurations with small edges are candidates
can_drop = true;
} else if (db::vprod_sign (p2 - p1, p1 - p0) < 0) {
@ -839,19 +841,19 @@ smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon
}
db::Polygon
smooth (const db::Polygon &polygon, db::Coord d)
smooth (const db::Polygon &polygon, db::Coord d, bool keep_hv)
{
db::Polygon new_poly;
std::vector <db::Point> new_pts;
smooth_contour (polygon.begin_hull (), polygon.end_hull (), new_pts, d);
smooth_contour (polygon.begin_hull (), polygon.end_hull (), new_pts, d, keep_hv);
if (new_pts.size () >= 3) {
new_poly.assign_hull (new_pts.begin (), new_pts.end (), false /*don't compress*/);
for (unsigned int h = 0; h < polygon.holes (); ++h) {
new_pts.clear ();
smooth_contour (polygon.begin_hole (h), polygon.end_hole (h), new_pts, d);
smooth_contour (polygon.begin_hole (h), polygon.end_hole (h), new_pts, d, keep_hv);
if (new_pts.size () >= 3) {
new_poly.insert_hole (new_pts.begin (), new_pts.end (), false /*don't compress*/);
}

View File

@ -449,6 +449,8 @@ db::Polygon DB_PUBLIC compute_rounded (const db::Polygon &poly, double rinner, d
*/
db::DPolygon DB_PUBLIC compute_rounded (const db::DPolygon &poly, double rinner, double router, unsigned int n);
#define KLAYOUT_SMOOTH_HAS_KEEP_HV 1
/**
* @brief Smooth a contour
*
@ -458,13 +460,14 @@ db::DPolygon DB_PUBLIC compute_rounded (const db::DPolygon &poly, double rinner,
* @param to The end of the contour
* @param new_pts The points that make up the new contour
* @param d The distance that determines the smoothing "roughness"
* @param keep_hv If true, vertical and horizontal edges are maintained
*/
void DB_PUBLIC smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon_contour_iterator to, std::vector <db::Point> &new_pts, db::Coord d);
void DB_PUBLIC smooth_contour (db::Polygon::polygon_contour_iterator from, db::Polygon::polygon_contour_iterator to, std::vector <db::Point> &new_pts, db::Coord d, bool keep_hv);
/**
* @brief Smooth a polygon (apply smoothing to the whole polygon)
*/
db::Polygon DB_PUBLIC smooth (const db::Polygon &poly, db::Coord d);
db::Polygon DB_PUBLIC smooth (const db::Polygon &poly, db::Coord d, bool keep_hv);
/**
* @brief Returns a value indicating whether the polygon is an "strange polygon"

View File

@ -263,15 +263,15 @@ Region::rounded_corners (double rinner, double router, unsigned int n) const
}
void
Region::smooth (coord_type d)
Region::smooth (coord_type d, bool keep_hv)
{
process (SmoothingProcessor (d));
process (SmoothingProcessor (d, keep_hv));
}
Region
Region::smoothed (coord_type d) const
Region::smoothed (coord_type d, bool keep_hv) const
{
return processed (SmoothingProcessor (d));
return processed (SmoothingProcessor (d, keep_hv));
}
void

View File

@ -1518,14 +1518,14 @@ public:
/**
* @brief Smoothes the region (in-place)
*/
void smooth (coord_type d);
void smooth (coord_type d, bool keep_hv);
/**
* @brief Returns the smoothed region
*
* @param d The smoothing accuracy
*/
Region smoothed (coord_type d) const;
Region smoothed (coord_type d, bool keep_hv) const;
/**
* @brief Returns the nth polygon

View File

@ -868,14 +868,14 @@ StrangePolygonCheckProcessor::process (const db::Polygon &poly, std::vector<db::
// -------------------------------------------------------------------------------------------------------------
// Smoothing processor
SmoothingProcessor::SmoothingProcessor (db::Coord d) : m_d (d) { }
SmoothingProcessor::SmoothingProcessor (db::Coord d, bool keep_hv) : m_d (d), m_keep_hv (keep_hv) { }
SmoothingProcessor::~SmoothingProcessor () { }
void
SmoothingProcessor::process (const db::Polygon &poly, std::vector<db::Polygon> &res) const
{
res.push_back (db::smooth (poly, m_d));
res.push_back (db::smooth (poly, m_d, m_keep_hv));
}
// -------------------------------------------------------------------------------------------------------------

View File

@ -504,7 +504,7 @@ class DB_PUBLIC SmoothingProcessor
: public PolygonProcessorBase
{
public:
SmoothingProcessor (db::Coord d);
SmoothingProcessor (db::Coord d, bool keep_hv);
~SmoothingProcessor ();
virtual void process (const db::Polygon &poly, std::vector<db::Polygon> &res) const;
@ -517,6 +517,7 @@ public:
private:
db::Coord m_d;
bool m_keep_hv;
db::MagnificationReducer m_vars;
};

View File

@ -185,10 +185,10 @@ static db::CompoundRegionOperationNode *new_strange_polygons_filter (db::Compoun
return new db::CompoundRegionProcessingOperationNode (new db::StrangePolygonCheckProcessor (), input, true /*processor is owned*/);
}
static db::CompoundRegionOperationNode *new_smoothed (db::CompoundRegionOperationNode *input, db::Coord d)
static db::CompoundRegionOperationNode *new_smoothed (db::CompoundRegionOperationNode *input, db::Coord d, bool keep_hv)
{
check_non_null (input, "input");
return new db::CompoundRegionProcessingOperationNode (new db::SmoothingProcessor (d), input, true /*processor is owned*/, d);
return new db::CompoundRegionProcessingOperationNode (new db::SmoothingProcessor (d, keep_hv), input, true /*processor is owned*/, d);
}
static db::CompoundRegionOperationNode *new_rounded_corners (db::CompoundRegionOperationNode *input, double rinner, double router, unsigned int n)
@ -572,9 +572,10 @@ Class<db::CompoundRegionOperationNode> decl_CompoundRegionOperationNode ("db", "
"@brief Creates a node extracting strange polygons.\n"
"'strange polygons' are ones which cannot be oriented - e.g. '8' shape polygons."
) +
gsi::constructor ("new_smoothed", &new_smoothed, gsi::arg ("input"), gsi::arg ("d"),
gsi::constructor ("new_smoothed", &new_smoothed, gsi::arg ("input"), gsi::arg ("d"), gsi::arg ("keep_hv", false),
"@brief Creates a node smoothing the polygons.\n"
"@param d The tolerance to be applied for the smoothing."
"@param d The tolerance to be applied for the smoothing.\n"
"@param keep_hv If true, horizontal and vertical edges are maintained.\n"
) +
gsi::constructor ("new_rounded_corners", &new_rounded_corners, gsi::arg ("input"), gsi::arg ("rinner"), gsi::arg ("router"), gsi::arg ("n"),
"@brief Creates a node generating rounded corners.\n"

View File

@ -1602,9 +1602,9 @@ static db::Polygon transformed_icplx_dp (const db::Polygon *p, const db::ICplxTr
return p->transformed (t, false /*don't compress*/);
}
static db::Polygon smooth (const db::Polygon *p, db::Coord d)
static db::Polygon smooth (const db::Polygon *p, db::Coord d, bool keep_hv)
{
return db::smooth (*p, d);
return db::smooth (*p, d, keep_hv);
}
static db::Polygon minkowsky_sum_pe (const db::Polygon *p, const db::Edge &e, bool rh)
@ -1787,17 +1787,18 @@ Class<db::Polygon> decl_Polygon ("db", "Polygon",
"\n"
"This method was introduced in version 0.22.\n"
) +
method_ext ("smooth", &smooth, gsi::arg ("d"),
method_ext ("smooth", &smooth, gsi::arg ("d"), gsi::arg ("keep_hv", false),
"@brief Smoothes a polygon\n"
"\n"
"Remove vertices that deviate by more than the distance d from the average contour.\n"
"The value d is basically the roughness which is removed.\n"
"\n"
"@param d The smoothing \"roughness\".\n"
"@param keep_hv If true, horizontal and vertical edges will be preserved always.\n"
"\n"
"@return The smoothed polygon.\n"
"\n"
"This method was introduced in version 0.23.\n"
"This method was introduced in version 0.23. The 'keep_hv' optional parameter was added in version 0.27.\n"
) +
method_ext ("minkowsky_sum", &minkowsky_sum_pe, gsi::arg ("e"), gsi::arg ("resolve_holes"),
"@brief Computes the Minkowsky sum of the polygon and an edge\n"

View File

@ -1412,9 +1412,10 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"See \\round_corners for a description of this method. This version returns a new region instead of "
"modifying self (out-of-place)."
) +
method ("smooth", &db::Region::smooth, gsi::arg ("d"),
method ("smooth", &db::Region::smooth, gsi::arg ("d"), gsi::arg ("keep_hv", false),
"@brief Smoothing\n"
"@param d The smoothing tolerance (in database units)\n"
"@param keep_hv If true, horizontal and vertical edges are maintained\n"
"\n"
"This method will simplify the merged polygons of the region by removing vertexes if the "
"resulting polygon stays equivalent with the original polygon. Equivalence is measured "
@ -1423,9 +1424,10 @@ Class<db::Region> decl_Region (decl_dbShapeCollection, "db", "Region",
"This method modifies the region. \\smoothed is a method that does the same but returns a new "
"region without modifying self. Merged semantics applies for this method.\n"
) +
method ("smoothed", &db::Region::smoothed, gsi::arg ("d"),
method ("smoothed", &db::Region::smoothed, gsi::arg ("d"), gsi::arg ("keep_hv", false),
"@brief Smoothing\n"
"@param d The smoothing tolerance (in database units)\n"
"@param keep_hv If true, horizontal and vertical edges are maintained\n"
"\n"
"See \\smooth for a description of this method. This version returns a new region instead of "
"modifying self (out-of-place). It has been introduced in version 0.25."

View File

@ -733,7 +733,8 @@ TEST(11_RoundAndSmoothed)
r1_sized -= r1;
db::Region rounded = r1_sized.rounded_corners (3000, 5000, 100);
db::Region smoothed = rounded.smoothed (100);
db::Region smoothed = rounded.smoothed (100, false);
db::Region smoothed_keep_hv = rounded.smoothed (100, true);
db::Layout target;
unsigned int target_top_cell_index = target.add_cell (ly.cell_name (top_cell_index));
@ -741,6 +742,7 @@ TEST(11_RoundAndSmoothed)
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (10, 0)), r1_sized);
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (11, 0)), rounded);
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (12, 0)), smoothed);
target.insert (target_top_cell_index, target.get_layer (db::LayerProperties (13, 0)), smoothed_keep_hv);
CHECKPOINT();
db::compare_layouts (_this, target, tl::testsrc () + "/testdata/algo/deep_region_au11.gds");

View File

@ -1188,8 +1188,8 @@ TEST(100)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 5).to_string (), "(0,-100;0,0;50,10;100,-10;150,0;150,-100)");
EXPECT_EQ (smooth (p, 20).to_string (), "(0,-100;0,0;150,0;150,-100)");
EXPECT_EQ (smooth (p, 5, true).to_string (), "(0,-100;0,0;50,10;100,-10;150,0;150,-100)");
EXPECT_EQ (smooth (p, 20, true).to_string (), "(0,-100;0,0;150,0;150,-100)");
}
// smoothing
@ -1207,8 +1207,8 @@ TEST(101)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 5).to_string (), "(100,-10;50,10;0,0;0,100;150,100;150,0)");
EXPECT_EQ (smooth (p, 20).to_string (), "(0,0;0,100;150,100;150,0)");
EXPECT_EQ (smooth (p, 5, true).to_string (), "(100,-10;50,10;0,0;0,100;150,100;150,0)");
EXPECT_EQ (smooth (p, 20, true).to_string (), "(0,0;0,100;150,100;150,0)");
}
// smoothing
@ -1224,8 +1224,8 @@ TEST(102)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 20).to_string (), "()");
EXPECT_EQ (smooth (p, 5).to_string (), "(100,-10;150,0;0,0;50,10)");
EXPECT_EQ (smooth (p, 20, true).to_string (), "()");
EXPECT_EQ (smooth (p, 5, true).to_string (), "(100,-10;150,0;0,0;50,10)");
}
// smoothing
@ -1251,9 +1251,9 @@ TEST(103)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 0).to_string (), "(59881,-249925;56852,-237283;56961,-237258;60061,-236492;63152,-235686;66231,-234839;69300,-233952;69407,-233919;73105,-246382;72992,-246417;69760,-247351;66516,-248243;63261,-249092;59995,-249899)");
EXPECT_EQ (smooth (p, 50).to_string (), "(59881,-249925;56852,-237283;63152,-235686;69407,-233919;73105,-246382;69760,-247351)");
EXPECT_EQ (smooth (p, 5000).to_string (), "(59881,-249925;56852,-237283;69407,-233919;73105,-246382)");
EXPECT_EQ (smooth (p, 0, true).to_string (), "(59881,-249925;56852,-237283;56961,-237258;60061,-236492;63152,-235686;66231,-234839;69300,-233952;69407,-233919;73105,-246382;72992,-246417;69760,-247351;66516,-248243;63261,-249092;59995,-249899)");
EXPECT_EQ (smooth (p, 50, true).to_string (), "(59881,-249925;56852,-237283;63152,-235686;69407,-233919;73105,-246382;69760,-247351)");
EXPECT_EQ (smooth (p, 5000, true).to_string (), "(59881,-249925;56852,-237283;69407,-233919;73105,-246382)");
}
// smoothing
@ -1272,7 +1272,8 @@ TEST(104)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 12).to_string (), "(-244,-942;-942,-246;248,943;943,246)");
EXPECT_EQ (smooth (p, 12, false).to_string (), "(-244,-942;-942,-246;248,943;943,246)");
EXPECT_EQ (smooth (p, 12, true).to_string (), "(-245,-942;-942,-247;-942,-246;247,943;248,943;943,246;-244,-942)");
}
// smoothing
@ -1292,11 +1293,46 @@ TEST(105)
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 0).to_string (), "(0,0;0,1000;100,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 50).to_string (), "(0,0;0,1000;100,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 80).to_string (), "(0,0;0,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 90).to_string (), "(0,0;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 100).to_string (), "(0,0;0,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 0, false).to_string (), "(0,0;0,1000;100,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 50, false).to_string (), "(0,0;0,1000;100,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 80, false).to_string (), "(0,0;0,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 90, false).to_string (), "(0,0;100,1100;800,1100;800,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 100, false).to_string (), "(0,0;0,1000;2000,1000;2000,0)");
EXPECT_EQ (smooth (p, 100, true).to_string (), "(0,0;0,1000;100,1000;100,1100;800,1100;800,1000;2000,1000;2000,0)");
}
// smoothing
TEST(106)
{
db::Point pattern [] = {
db::Point (0, 0),
db::Point (0, 73235),
db::Point (100, 74568),
db::Point (700, 82468),
db::Point (1200, 90468),
db::Point (2000, 106468),
db::Point (2300, 114468),
db::Point (2700, 130468),
db::Point (2800, 138468),
db::Point (2800, 154468),
db::Point (2700, 162468),
db::Point (2300, 178468),
db::Point (2000, 186468),
db::Point (1200, 202468),
db::Point (700, 210468),
db::Point (100, 218368),
db::Point (0, 219701),
db::Point (0, 272971),
db::Point (126450, 272971),
db::Point (126450, 0),
};
db::Polygon p;
p.assign_hull (&pattern[0], &pattern[0] + sizeof (pattern) / sizeof (pattern[0]));
EXPECT_EQ (smooth (p, 0, false).to_string (), "(0,0;0,73235;100,74568;700,82468;1200,90468;2000,106468;2300,114468;2700,130468;2800,138468;2800,154468;2700,162468;2300,178468;2000,186468;1200,202468;700,210468;100,218368;0,219701;0,272971;126450,272971;126450,0)");
EXPECT_EQ (smooth (p, 100, false).to_string (), "(0,0;100,74568;1200,90468;2300,114468;2800,138468;2700,162468;2000,186468;700,210468;0,219701;0,272971;126450,272971;126450,0)");
EXPECT_EQ (smooth (p, 100, true).to_string (), "(0,0;0,73235;1200,90468;2300,114468;2800,138468;2800,154468;2000,186468;700,210468;0,219701;0,272971;126450,272971;126450,0)");
}
// rounding
@ -1501,7 +1537,7 @@ TEST(203)
in.push_back (pp);
ep.simple_merge (in, out, false /*no cut line*/);
pp = out.front ();
pp = smooth (pp, 1);
pp = smooth (pp, 1, true);
EXPECT_EQ (pp.hull ().size (), size_t (300));
EXPECT_EQ (extract_rad (pp, rinner, router, n, &pr), true);
@ -1547,7 +1583,7 @@ TEST(204)
in.push_back (pp);
ep.simple_merge (in, out, false /*no cut line*/);
pp = out.front ();
pp = smooth (pp, 1);
pp = smooth (pp, 1, true);
EXPECT_EQ (pp.hull ().size (), size_t (200));
EXPECT_EQ (extract_rad (pp, rinner, router, n, &pr), true);

View File

@ -739,16 +739,18 @@ CODE
# %DRC%
# @name smoothed
# @brief Applies smoothing
# @synopsis expression.smoothed(d)
# @synopsis expression.smoothed(d [, keep_hv ])
#
# This operation acts on polygons and applies polygon smoothing with the tolerance d. See \Layer#smoothed for more details.
# This operation acts on polygons and applies polygon smoothing with the tolerance d. 'keep_hv' indicates
# whether horizontal and vertical edges are maintained. Default is 'no' which means such edges may be distorted.
# See \Layer#smoothed for more details.
#
# The "smoothed" method is available as a plain function or as a method on \DRC# expressions.
# The plain function is equivalent to "primary.smoothed".
def smoothed(d)
def smoothed(d, keep_hv = false)
@engine._context("smoothed") do
DRCOpNodeFilter::new(@engine, self, :new_smoothed, "smoothed", @engine._make_value(d))
DRCOpNodeFilter::new(@engine, self, :new_smoothed, "smoothed", @engine._make_value(d), keep_hv)
end
end

View File

@ -1387,7 +1387,7 @@ MainService::cm_round_corners ()
std::vector <db::Polygon> in;
ep.merge (primary, in, 0 /*min_wc*/, false /*resolve holes*/, true /*min coherence*/);
for (std::vector <db::Polygon>::iterator p = in.begin (); p != in.end (); ++p) {
*p = smooth (*p, 1);
*p = smooth (*p, 1, true);
}
std::vector <db::Polygon> out = in;

Binary file not shown.

View File

@ -289,6 +289,9 @@ class DBPolygonTests(unittest.TestCase):
p = pya.Polygon( [ pya.Point.new(0, 0), pya.Point.new(10, 50), pya.Point.new(0, 100), pya.Point.new(200, 100), pya.Point.new(200, 0) ])
self.assertEqual(str(p.smooth(5)), "(0,0;10,50;0,100;200,100;200,0)")
self.assertEqual(str(p.smooth(15)), "(0,0;0,100;200,100;200,0)")
p = pya.Polygon( [ pya.Point.new(0, 0), pya.Point.new(10, 50), pya.Point.new(10, 100), pya.Point.new(200, 100), pya.Point.new(200, 0) ])
self.assertEqual(str(p.smooth(15, False)), "(0,0;10,100;200,100;200,0)")
self.assertEqual(str(p.smooth(15, True)), "(0,0;10,50;10,100;200,100;200,0)")
# Ellipse constructor
p = pya.Polygon.ellipse( pya.Box(-10000, -20000, 30000, 40000), 200 )

View File

@ -304,6 +304,9 @@ class DBPolygon_TestClass < TestBase
p = RBA::Polygon::new( [ RBA::Point.new(0, 0), RBA::Point.new(10, 50), RBA::Point.new(0, 100), RBA::Point.new(200, 100), RBA::Point.new(200, 0) ])
assert_equal(p.smooth(5).to_s, "(0,0;10,50;0,100;200,100;200,0)")
assert_equal(p.smooth(15).to_s, "(0,0;0,100;200,100;200,0)")
p = RBA::Polygon::new( [ RBA::Point.new(0, 0), RBA::Point.new(10, 50), RBA::Point.new(10, 100), RBA::Point.new(200, 100), RBA::Point.new(200, 0) ])
assert_equal(p.smooth(15, false).to_s, "(0,0;10,100;200,100;200,0)")
assert_equal(p.smooth(15, true).to_s, "(0,0;10,50;10,100;200,100;200,0)")
# Ellipse constructor
p = RBA::Polygon::ellipse( RBA::Box::new(-10000, -20000, 30000, 40000), 200 )