/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: SeaOfGatesEngine.java
 * Routing tool: Sea of Gates routing
 * Written by: Steven M. Rubin
 *
 * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) 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.
 *
 * Electric(tm) 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 <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.routing.seaOfGates;

import com.sun.electric.database.EditingPreferences;
import com.sun.electric.database.Environment;
import com.sun.electric.database.ImmutableNodeInst;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.geometry.PolyBase;
import com.sun.electric.database.geometry.PolyBase.Point;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.id.ArcProtoId;
import com.sun.electric.database.id.CellId;
import com.sun.electric.database.id.NodeProtoId;
import com.sun.electric.database.id.PortProtoId;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.Name;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.Connection;
import com.sun.electric.database.topology.Geometric;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.topology.RTBounds;
import com.sun.electric.database.topology.RTNode;
import com.sun.electric.database.topology.SteinerTree;
import com.sun.electric.database.topology.SteinerTree.SteinerTreePort;
import com.sun.electric.database.topology.SteinerTree.SteinerTreePortPair;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.technology.ArcProto;
import com.sun.electric.technology.DRCTemplate;
import com.sun.electric.technology.Layer;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.SizeOffset;
import com.sun.electric.technology.Technology;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.tool.drc.DRC;
import com.sun.electric.tool.routing.SeaOfGates;
import com.sun.electric.tool.routing.SeaOfGates.SeaOfGatesArcProperties;
import com.sun.electric.tool.routing.SeaOfGates.SeaOfGatesCellParameters;
import com.sun.electric.tool.user.ErrorLogger;
import com.sun.electric.tool.user.ui.RoutingDebug;
import com.sun.electric.util.ElapseTimer;
import com.sun.electric.util.TextUtils;
import com.sun.electric.util.math.DBMath;
import com.sun.electric.util.math.FixpCoord;
import com.sun.electric.util.math.FixpRectangle;
import com.sun.electric.util.math.FixpTransform;
import com.sun.electric.util.math.GenMath;
import com.sun.electric.util.math.MutableBoolean;
import com.sun.electric.util.math.MutableDouble;
import com.sun.electric.util.math.MutableInteger;
import com.sun.electric.util.math.Orientation;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Class to do sea-of-gates routing. This router replaces unrouted arcs with real geometry.
 * It has these features:
 * <ul>
 * <li> The router only works in layout, and only routes metal wires.
 * <li> The router uses vias to move up and down the metal layers.
 * <li> Understands multiple vias and multiple via orientations.
 * <li> The router is not tracked: it runs gridless on the Electric database
 * <li> Favors wires on full-grid units
 * <li> Tries to cover multiple grid units in a single jump
 * <li> Routes power and ground first, then goes by length (shortest nets first)
 * <li> Uses Steiner Trees to reorganize a network into short segments
 * <li> Can detect "spine" situations and route specially
 * <li> Global router assigns specific paths to each route
 * <li> Can prefer to run odd metal layers in one axis (default horizontal), even layers on the other axis (default vertical)
 * <li> Routes in both directions (alternating steps from A to B and from B to A) and stops when one direction completes
 * <li> Parallel option runs both wavefronts at once and aborts the slower one
 * <li> Users can request that some layers not be used, can request that some layers be favored
 * <li> Routes are made as wide as the widest arc already connected to any point
 * <li> User preference can limit width
 * <li> Cost penalty also includes space left in the track on either side of a segment
 * <li> Is able to connect to anything on the destination network, not just the destination port
 * </ul>
 *
 * Things to do:
 *  Fix ability to route to any point on destination (ANYPOINTONDESTINATION=true)
 *  Improve Global routing
 *  Ability to route to any SearchVertex in opposing direction
 *  Weight layer changes higher if far from end layer and going away
 *  Detect "river routes" and route specially
 *  Rip-up
 *  Sweep cost parameters (with parallel processors) to dynamically find best settings
 */
public abstract class SeaOfGatesEngine
{
	public static final boolean DEBUGGRIDDING = false;
	public static final boolean NEWBLOCKAGE = true;
	/** true to route to any point on the destination */		private static final boolean ANYPOINTONDESTINATION = true;
	/** true to stick to destination edge */					private static final boolean STICKTOJUMPEDGES = false;
	/** the height/width ratio that defines a spine */			private static final double SPINERATIO = 50;

	/** Granularity of coordinates. */							private static final double GRAINSIZE = 1;
	/** Cost: of forcing horizontal/vertical metal layers */	private static final int COSTALTERNATINGMETAL = 20;
	/** Cost of changing layers. */								private static final int COSTLAYERCHANGE = 8;
	/** Cost of routing away from the target. */				private static final int COSTWRONGDIRECTION = 15;
	/** Cost of running on non-favored layer. */				private static final int COSTUNFAVORED = 10;
	/** Cost of making a turn. */								private static final int COSTTURNING = 1;
	/** Cost of having coordinates that are off-grid. */		private static final int COSTOFFGRID = 15;

	/** Bit set in network ID for virtual blockage. */			private static final int OFFSETVIRTUAL = 1;
	/** Bit set in network ID for blockage on end A. */			private static final int OFFSETENDA = 2;
	/** Bit set in network ID for blockage on end B. */			private static final int OFFSETENDB = 4;

	public static SearchVertex svAborted = new SearchVertex(0, 0, 0, 0, null, null, 0, null);
	public static SearchVertex svExhausted = new SearchVertex(0, 0, 0, 0, null, null, 0, null);
	public static SearchVertex svLimited = new SearchVertex(0, 0, 0, 0, null, null, 0, null);
	public static SearchVertex svAbandoned = new SearchVertex(0, 0, 0, 0, null, null, 0, null);

	/** number of metal layers in the technology. */			private static int numMetalLayers;

	/** Environment */                                          protected Environment env;
	/** Cell in which routing occurs. */						private Cell cell;
	/** true to run to/from and from/to routing in parallel */	private boolean parallelDij;
	/** for logging errors */									private ErrorLogger errorLogger;
	/** Cell size. */											private Rectangle2D cellBounds;
	/** Technology to use for routing. */						private Technology tech;
	/** metal layers in the technology. */						private Layer[] metalLayers;
	/** via layers in the technology. */						private Layer[] viaLayers;
	/** arcs to use for each metal layer. */					private ArcProto[] metalArcs;
	/** favoritism for each metal layer. */						private boolean[] favorArcs;
	/** avoidance for each metal layer. */						private boolean[] preventArcs;
	/** vias to use to go up from each metal layer. */			private MetalVias[] metalVias;
	/** metal gridding for the cell. */							private double[][] metalGrid;
	/** metal gridding range for the cell. */					private double[] metalGridRange;
	/** maximum possible DRC surround around the metal. */      private double[] metalSurround;
	/** worst spacing rule for a given metal layer. */			private double[] worstMetalSurround;
	/** minimum spacing between the centers of two vias. */		private double[] viaSurround;
	/** R-Trees for routing blockages */						private BlockageTrees rTrees;
	/** converts Networks to unique integers */					private Map<Network, Integer> netIDs;
	/** preferences */											private SeaOfGates.SeaOfGatesOptions prefs;
	/** interaction with outer environment */                   public Handler handler;
	/** EditingPreferences */                                   private EditingPreferences ep;
	/** cell-specific parameters */								private SeaOfGatesCellParameters sogp;
	/** taps that need to be added to spine routes */			private List<NeededRoute> tapRoutes;
	/** true to keep messages quiet */							private boolean messagesQuiet;
	/** total number of blockages to node-extract */			private int totalBlockages;
	/** number of blockages node-extracted so far */			private int blockagesFound;
	/** minimum spacing between this metal and itself. */		private Map<Double, Map<Double, double[]>>[] layerSurround;
	/** the total length of routing */							private double totalWireLength;

	/************************************** CONTROL **************************************/

	/**
	 * This is the public interface for Sea-of-Gates Routing when done in batch mode.
	 * @param handler interaction with outer environment
	 * @param cell the cell to be Sea-of-Gates-routed.
	 */
	public void routeIt(Handler handler, Cell cell, boolean quiet) {
		routeIt(handler, cell, quiet, null);
	}

	/**
	 * This is the public interface for Sea-of-Gates Routing when done in batch mode.
	 * @param handler interaction with outer environment
	 * @param cell the cell to be Sea-of-Gates-routed.
	 * @param arcsToRoute a List of ArcInsts on networks to be routed.
	 */
	public void routeIt(Handler handler, Cell cell, boolean quiet, List<ArcInst> arcsToRoute)
	{
		routeIt(handler, cell, quiet, arcsToRoute, new SeaOfGatesCellParameters(cell));
	}

	/**
	 * This is the public interface for Sea-of-Gates Routing when done in batch mode.
	 * @param handler interaction with outer environment
	 * @param cell the cell to be Sea-of-Gates-routed.
	 * @param arcsToRoute a List of ArcInsts on networks to be routed.
	 * @param sogp parameters to use
	 */
	public void routeIt(Handler handler, Cell cell, boolean quiet, List<ArcInst> arcsToRoute, SeaOfGatesCellParameters sogp)
	{
		messagesQuiet = quiet;
		totalWireLength = 0;
		// initialize routing
		this.handler = handler;
		ep = handler.getEditingPreferences();
		env = cell.getDatabase().getEnvironment();
		this.sogp = sogp;
		this.tapRoutes = new ArrayList<NeededRoute>();

		this.cell = cell;
		cellBounds = cell.getBounds();
		tech = cell.getTechnology();

		if (initializeDesignRules()) return;
		initializeGrids();
		netIDs = new HashMap<Network, Integer>();
		errorLogger = ErrorLogger.newInstance("Routing (Sea of gates) " + cell.describe(false));
		prefs.theTimer = ElapseTimer.createInstance().start();
		Netlist netList = cell.getNetlist();

		// get arcs to route
		Set<String> netsToRoute = sogp.getNetsToRoute();
		if (netsToRoute != null && netsToRoute.size() > 0)
		{
			// overriding nets are listed in the Sea-of-Gates Cell Properties, see if the user selected an overriding subset
			boolean allSelected = true;
			if (arcsToRoute != null)
			{
				for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
				{
					ArcInst ai = it.next();
					if (ai.getProto() != Generic.tech().unrouted_arc) continue;
					if (!arcsToRoute.contains(ai)) { allSelected = false;  break; }
				}
			}
			if (allSelected)
			{
				arcsToRoute = new ArrayList<ArcInst>();
				for(String netName : netsToRoute)
				{
					ArcInst ai = cell.findArc(netName);
					if (ai == null)
					{
						System.out.println("WARNING: Could not find network '" + netName + "' which was requested by the Sea-of-Gates Cell Properties");
						continue;
					}
					Network net = netList.getNetwork(ai, 0);
					if (net == null) continue;
					for(Iterator<ArcInst> it = net.getArcs(); it.hasNext() ;)
					{
						ai = it.next();
						arcsToRoute.add(ai);
					}
				}
				System.out.println("Routing " + arcsToRoute.size() + " arcs from the list given in the Sea-of-Gates Cell Properties");
			}
		}
		if (arcsToRoute == null)
		{
			arcsToRoute = new ArrayList<ArcInst>();
			for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
			{
				ArcInst ai = it.next();
				if (ai.getProto() != Generic.tech().unrouted_arc) continue;
				arcsToRoute.add(ai);
			}
		}
		if (arcsToRoute.isEmpty()) return;

		// organize routes by networks and build routing data structures
		setProgressNote("Make list of routes...");
		if (!RoutingDebug.isActive())
			handler.startProgressDialog("Routing " + arcsToRoute.size() + " nets in cell " + cell.describe(false));
		List<EPoint> linesInNonMahnattan = new ArrayList<EPoint>();
		RouteBatch[] routeBatches = makeListOfRoutes(netList, arcsToRoute, linesInNonMahnattan);
		MutableBoolean hadNonmanhattan = new MutableBoolean(linesInNonMahnattan.size() > 0);

		if (routeBatches.length == 0) return;
		if (RoutingDebug.isRewireNetworks())
		{
			rewireNetworks(routeBatches);
			return;
		}
		List<NeededRoute> allRoutes = new ArrayList<NeededRoute>();
		for(int b=0; b<routeBatches.length; b++)
			for(NeededRoute nr : routeBatches[b].routesInBatch) allRoutes.add(nr);
		info("");
		info("Sea-of-gates router finding " + allRoutes.size() + " paths on " + routeBatches.length + " networks in cell " + cell.describe(false));
		if (hadNonmanhattan.booleanValue())
		{
			String info = "Found nonmanhattan geometry. This will cause large rectangular blockages, which may block too much.";
			warn(info);
			errorLogger.logMessageWithLines(info, linesInNonMahnattan, linesInNonMahnattan, cell, 0, false);
		}

		// do "global routing" preprocessing
		if (prefs.useGlobalRouter || RoutingDebug.isTestGlobalRouting())
		{
			setProgressNote("Do Global Routing...");
			info("Doing Global Routing...");

			// in debug mode, construct fake routes for everything in the cell
			RouteBatch[] fakeRBs = null;
			if (RoutingDebug.isActive())
			{
				Map<Network,RouteBatch> additionalRBs = new HashMap<Network,RouteBatch>();
				Set<ArcInst> arcsInCell = new HashSet<ArcInst>();
				for(ArcInst ai : arcsToRoute) arcsInCell.add(ai);
				for (Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); )
				{
					ArcInst ai = it.next();
					if (ai.getProto() != Generic.tech().unrouted_arc) continue;
					if (arcsInCell.contains(ai)) continue;
					Network net = netList.getNetwork(ai, 0);
					RouteBatch rb = additionalRBs.get(net);
					if (rb == null) additionalRBs.put(net, rb = new RouteBatch());

					// get Arc information about the ends of the path
					PortInst aPi = ai.getHeadPortInst();
					PortInst bPi = ai.getTailPortInst();
					ArcProto aArc = getMetalArcOnPort(aPi);
					if (aArc == null) continue;
					ArcProto bArc = getMetalArcOnPort(bPi);
					if (bArc == null) continue;

					// create the fake NeededRoute
					NeededRoute nr = new NeededRoute(net.getName(), aPi, bPi, aArc, bArc, null, 0);
					rb.addRoute(nr);
				}
				fakeRBs = new RouteBatch[additionalRBs.size()];
				int i = 0;
				for(Network net : additionalRBs.keySet()) fakeRBs[i++] = additionalRBs.get(net);
			}

			// do the global routing
			double wirePitch = metalSurround[0] + metalArcs[0].getDefaultLambdaBaseWidth(ep);
			GlobalRouter gr = doGlobalRouting(cell, routeBatches, fakeRBs, wirePitch);

			// reorder so that paths without Global Routing (small ones) come first
			List<NeededRoute> withGR = new ArrayList<NeededRoute>();
			List<NeededRoute> withoutGR = new ArrayList<NeededRoute>();
			for(NeededRoute nr : allRoutes)
			{
				if (nr.buckets != null) withGR.add(nr); else withoutGR.add(nr);
			}
			info("Global Routing planned " + withGR.size() + " paths in " + gr.getXBuckets() + "x" + gr.getYBuckets() +
				" buckets (" + withoutGR.size() + " paths are too short to route globally)");

			// reorder the routes so that globally-routed nets come first or last
//			allRoutes.clear();
//			for(NeededRoute nr : withGR) allRoutes.add(nr);
//			for(NeededRoute nr : withoutGR) allRoutes.add(nr);

			if (RoutingDebug.isActive()) RoutingDebug.setGlobalRouting(gr);
			if (RoutingDebug.isTestGlobalRouting())
			{
				RoutingDebug.showGlobalRouting();
				return;
			}
			setProgressNote("Detail Route " + allRoutes.size() + " paths...");
			info("Detail Routing " + allRoutes.size() + " paths...");
		} else
		{
			setProgressNote("Route " + allRoutes.size() + " paths...");
			info("Routing " + allRoutes.size() + " paths...");
		}
		for(NeededRoute nr : allRoutes)
		{
			if (nr.spineTaps != null) nr.routeName += " (spine)"; else
			{
				if (nr.batch.routesInBatch.size() > 1)
					nr.routeName += " (" + (nr.routeInBatch+1) + " of " + nr.batch.routesInBatch.size() + ")";
			}
		}

		// if debugging, stop now
		if (RoutingDebug.isActive() && allRoutes.size() > 0)
		{
			int whichRoute = RoutingDebug.getDesiredRouteToDebug();
			if (whichRoute < 0) whichRoute = 0;
			if (whichRoute >= allRoutes.size()) whichRoute = allRoutes.size() - 1;
			NeededRoute nr = allRoutes.get(whichRoute);

			if (RoutingDebug.isDisplayAreaBlockages())
			{
				RoutingDebug.showGeometryInArea(nr);
				return;
			}
			if (RoutingDebug.isDisplayEndBlockages())
			{
				RoutingDebug.showGeometryAtRouteEnds(nr);
				return;
			}
			if (RoutingDebug.isTestRoutingGrid())
			{
				RoutingDebug.showRoutingGrid(nr);
				return;
			}
			if (RoutingDebug.isDisplayRouting())
			{
				RoutingDebug.debugRoute(nr, allRoutes);
				return;
			}
		}

		// determine the kind of parallelism to use
		boolean parallel = prefs.useParallelRoutes;
		parallelDij = prefs.useParallelFromToRoutes;
		int numberOfProcessors = Runtime.getRuntime().availableProcessors();
		if (numberOfProcessors <= 1) parallelDij = false;

		// determine the number of parallel threads to use
		int numberOfThreads = numberOfProcessors;
		if (prefs.forcedNumberOfThreads > 0)
		{
			// user's input overrides thread-count computation
			info("Forcing use of " + prefs.forcedNumberOfThreads + " threads");
			numberOfThreads = prefs.forcedNumberOfThreads;
		}
		if (!parallel) numberOfThreads = 1;
		if (numberOfThreads == 1) {
			parallel = false;
			parallelDij = false;
		}
		if (parallel)
		{
			String message = "NOTE: System has " + numberOfProcessors + " processors so";
			if (parallelDij)
			{
				message += " routing " + (numberOfThreads/2) + " paths in parallel";
				message += " and routing both directions of each path in parallel";
			} else {
				message += " routing " + numberOfThreads + " paths in parallel";
			}
			info(message);
		}

		// do the routing
		if (numberOfThreads > 1) doRoutingParallel(numberOfThreads, allRoutes); else
			doRouting(allRoutes);
		handler.flush(true);

		if (tapRoutes.size() > 0)
		{
			setProgressNote("Adding taps to spine routes...");
			info("------------------ Adding taps to spine routes...");

			// do the routing again
			if (numberOfThreads > 1) doRoutingParallel(numberOfThreads, tapRoutes); else
				doRouting(tapRoutes);
			handler.flush(true);
		}

		// see if any routes failed and need to be redone
		List<NeededRoute> redoRoutes = new ArrayList<NeededRoute>();
		for (int b = 0; b < routeBatches.length; b++)
		{
			for(NeededRoute nr : routeBatches[b].routesInBatch)
			{
				if (nr.routedSuccess) continue;
				redoRoutes.add(nr);
				nr.buckets = null;
				nr.errorMessage = null;
				nr.complexityLimit = prefs.rerunComplexityLimit;
				nr.makeWavefronts();
			}
		}

		// redo the routing on the failed routes, ignoring global routing information
		if (prefs.reRunFailedRoutes && redoRoutes.size() > 0)
		{
			setProgressNote("Re-Route " + redoRoutes.size() + " paths...");
			info("------------------ Re-Route " + redoRoutes.size() + " paths...");

			// do the routing again
			for(NeededRoute nr : redoRoutes) nr.reroute = true;
			if (numberOfThreads > 1) doRoutingParallel(numberOfThreads, redoRoutes); else
				doRouting(redoRoutes);
			handler.flush(true);
		}

		// make new unrouted arcs for all failed routes
		RouteResolution resolution = new RouteResolution(cell.getId());
		for (int b = 0; b < routeBatches.length; b++)
		{
			for(NeededRoute nr : routeBatches[b].routesInBatch)
			{
				if (nr.routedSuccess) continue;
				resolution.addUnrouted(nr.aPi, nr.bPi);
			}
		}
		if (!DEBUGGRIDDING)
			handler.instantiate(resolution);
		handler.flush(true);

		// show statistics on the routing
		summarize(routeBatches, allRoutes);
		// dumpSpacing();

		// clean up
		handler.termLogging(errorLogger);
		handler.stopProgressDialog();
	}

	/**
	 * Method to return an R-Tree of blockages on a given metal Layer.
	 * @param lay the metal Layer to examine.
	 * @return an RTNode that is the top of the tree of blockages on that Layer.
	 */
	public RTNode<SOGBound> getMetalTree(Layer lay) { return rTrees.getMetalTree(lay).getRoot(); }

	public Iterator<SOGBound> searchMetalTree(Layer lay, Rectangle2D bound)
	{
		return rTrees.getMetalTree(lay).search(bound);
	}

	/**
	 * Method to rip-out all unrouted wires and replace them with efficiently-organized unrouted wires
	 * that follow the minimum-distance Steiner tree.
	 * @param routeBatches the batches of wires to rewire.
	 */
	private void rewireNetworks(RouteBatch[] routeBatches)
	{
		for (int b = 0; b < routeBatches.length; b++)
		{
			RouteBatch rb = routeBatches[b];
			RouteResolution res = rb.resolution;

			// remove the original unrouted nodes/arcs
			for (ArcInst aiKill : rb.unroutedArcs) res.killArc(aiKill);
			for (NodeInst niKill : rb.unroutedNodes) res.killNode(niKill);

			// now add new Unrouted arcs from the Steiner tree calculations
			for(NeededRoute nr : rb.routesInBatch)
				res.addUnrouted(nr.aPi, nr.bPi);
			handler.instantiate(res);
		}
		handler.flush(true);
		sogp.setSteinerDone(true);
		sogp.saveParameters(ep);
	}

	/**
	 * Method to describe the results of routing
	 */
	private void summarize(RouteBatch[] routeBatches, List<NeededRoute> allRoutes)
	{
		int numRoutedSegments = 0;
		int numFailedNets = 0;
		int numFailedBatches = 0;
		for (int b = 0; b < routeBatches.length; b++)
		{
			boolean failed = false;
			for(NeededRoute nr : routeBatches[b].routesInBatch)
			{
				if (nr.routedSuccess)
				{
					numRoutedSegments++;
				} else
				{
					if (nr.errorMessage != null)
					{
						failed = true;
						numFailedNets++;
					}
				}
			}
			if (failed) numFailedBatches++;
		}
		prefs.theTimer.end();
		info("Cell " + cell.describe(false) + " routed " + numRoutedSegments + " out of " + allRoutes.size() +
			" segments; total length of routed wires is " + formatDistance(totalWireLength) +
			" (took " + prefs.theTimer + ")");
		if (numFailedNets > 0) info("NOTE: " + numFailedNets + " segments on " + numFailedBatches + " nets were not routed");
	}

	/**
	 * Method to set the preferences in this SeaOfGatesEngine.
	 * @param p Preferences to use for routing.
	 */
	public void setPrefs(SeaOfGates.SeaOfGatesOptions p) { prefs = p; }

	/**
	 * Method to return the preferences in this SeaOfGatesEngine.
	 * @return the preferences to use for routing.
	 */
	public SeaOfGates.SeaOfGatesOptions getPrefs() { return prefs; }

	/**
	 * Method to return the technology considered in routing.
	 * @return the technology considered in routing.
	 */
	public Technology getTech() { return tech; }

	/**
	 * Method to return the number of metal layers being considered in routing.
	 * @return the number of metal layers being considered in routing.
	 */
	public int getNumMetals() { return numMetalLayers; }

	/**
	 * Method to return the metal Layer associated with a layer number.
	 * @param layNum a layer number, from 0 to getNumMetals()-1.
	 * @return the metal Layer being used on the layer number.
	 */
	public Layer getMetalLayer(int layNum) { return metalLayers[layNum]; }

	/**
	 * Method to return the via Layer associated with a layer number.
	 * @param layNum a layer number, from 0 to getNumMetals()-2.
	 * @return the via Layer being used on the layer number.
	 */
	public Layer getViaLayer(int layNum) { return viaLayers[layNum]; }

	/**
	 * Method to convert a distance to a string, using scale from the technology considered in routing.
	 * See com.sun.electric.util.TextUtils#formatDistance(double, Technology)
	 * @param v the distance value to format.
	 * @return the string representation of the number.
	 */
	protected String formatDistance(double v) {
		return com.sun.electric.database.text.TextUtils.formatDistance(v, tech);
	}

	/**
	 * Method to describe this NodeInst as a string.
	 * @param ni NodeInst to describe
	 * @return a description of this NodeInst as a string.
	 */
	protected String describe(NodeInst ni) {
		NodeProto np = ni.getProto();
		boolean libDescribe;
		if (np instanceof Cell) {
			libDescribe = ((Cell)np).getLibrary() != cell.getLibrary();
		} else {
			libDescribe = ((PrimitiveNode)np).getTechnology() != tech;
		}
		return libDescribe ? ni.libDescribe() : ni.noLibDescribe();
	}

	/**
	 * Method to describe this ArcInst as a string.
	 * @param ai ArcInst to describe
	 * @return a description of this ArcInst as a string.
	 */
	protected String describe(ArcInst ai) {
		ArcProto ap = ai.getProto();
		boolean libDescribe = ap.getTechnology() != tech;
		return libDescribe ? ai.libDescribe() : ai.noLibDescribe();
	}

	/**
	 * Method to describe this NodeProto as a string.
	 * @param np NodeProto to describe
	 * @return a description of this NodeProto as a string.
	 */
	protected String describe(NodeProto np) {
		boolean libDescribe;
		if (np instanceof Cell) {
			libDescribe = ((Cell)np).getLibrary() != cell.getLibrary();
		} else {
			libDescribe = ((PrimitiveNode)np).getTechnology() != tech;
		}
		return libDescribe ? np.libDescribe() : np.noLibDescribe();
	}

	/**
	 * Method to describe this ArcProto as a string.
	 * @param ap ArcProto to describe
	 * @return a description of this ArcProto as a string.
	 */
	protected String describe(ArcProto ap) {
		boolean libDescribe = ap.getTechnology() != tech;
		return libDescribe ? ap.getFullName() : ap.getName();
	}

	/**
	 * Check if we are scheduled to abort. If so, print message if non null and
	 * return true.
	 * @return true on abort, false otherwise. If job is scheduled for abort or
	 *		 aborted. and it will report it to standard output
	 */
	protected boolean checkAbort() {
		return handler.checkAbort();
	}

	/**
	 * Log a message at the TRACE level.
	 *
	 * @param msg the message string to be logged
	 */
	protected void trace(String msg) {
		handler.trace(msg);
	}

	/**
	 * Log a message at the DEBUG level.
	 *
	 * @param msg the message string to be logged
	 */
	protected void debug(String msg) {
		handler.debug(msg);
	}

	/**
	 * Log a message at the INFO level.
	 *
	 * @param msg the message string to be logged
	 */
	protected void info(String msg) {
		handler.info(msg);
	}

	/**
	 * Log a message at the WARN level.
	 *
	 * @param msg the message string to be logged
	 */
	protected void warn(String msg) {
		handler.warn(msg);
	}

	/**
	 * Log a message at the ERROR level.
	 *
	 * @param msg the message string to be logged
	 */
	protected void error(String msg) {
		handler.error(msg);
	}

	 /**
	 * Method to set a text message in the progress dialog.
	 * @param message the new progress message.
	 */
	protected void setProgressNote(String message) {
		if (!RoutingDebug.isActive())
			handler.setProgressNote(message);
	}

	/**
	 * Method to update the progress bar
	 * @param done the amount done (from 0 to total-1).
	 * @param total the total amount to do.
	 */
	protected void setProgressValue(int done, int total) {
		if (!RoutingDebug.isActive())
			handler.setProgressValue(done, total);
	}

	/**
	 * flush changes
	 */
	void flush() {
		handler.flush(false);
	}

	/**
	 * Stub for parallel routing.
	 * @param numberOfThreads number of threads to create.
	 * @param allRoutes the routes that need to be done.
	 */
	protected abstract void doRoutingParallel(int numberOfThreads, List<NeededRoute> allRoutes);

	/**
	 * Method to do the routing in a single thread.
	 * @param allRoutes the routes that need to be done.
	 */
	private void doRouting(List<NeededRoute> allRoutes)
	{
		int totalRoutes = allRoutes.size();
		for (int r = 0; r < totalRoutes; r++)
		{
			if (checkAbort())
			{
				info("Sea-of-gates routing aborted");
				break;
			}

			// get information on the segment to be routed
			NeededRoute nr = allRoutes.get(r);
			setProgressValue(r, totalRoutes);
			setProgressNote("Network " + nr.routeName);
			if (!messagesQuiet)
				trace("Routing network " + nr.routeName + "...");

			// route the segment
			Runnable[] runnables = findPath(nr);
			if (runnables != null) {
				for (Runnable runnable: runnables) {
					runnable.run();
				}
			}
		}
	}

	/**
	 * Public interface that encapsulates interaction of SeaOfGatesEngines with outer environment
	 */
	public static interface Handler {

		/**
		 * Returns EditingPreferences
		 * @return EditingPreferences
		 */
		EditingPreferences getEditingPreferences();

		/**
		 * Check if we are scheduled to abort. If so, print message if non null and
		 * return true.
		 * @return true on abort, false otherwise. If job is scheduled for abort or
		 *		 aborted. and it will report it to standard output
		 */
		boolean checkAbort();

		/**
		 * Log a message at the TRACE level.
		 *
		 * @param msg the message string to be logged
		 */
		void trace(String msg);

		/**
		 * Log a message at the DEBUG level.
		 *
		 * @param msg the message string to be logged
		 */
		void debug(String msg);

		/**
		 * Log a message at the INFO level.
		 *
		 * @param msg the message string to be logged
		 */
		void info(String msg);

		/**
		 * Log a message at the WARN level.
		 *
		 * @param msg the message string to be logged
		 */
		void warn(String msg);

		/**
		 * Log a message at the ERROR level.
		 *
		 * @param msg the message string to be logged
		 */
		void error(String msg);

		/**
		 * Method called when all errors are logged.  Initializes pointers for replay of errors.
		 */
		void termLogging(ErrorLogger errorLogger);

		/**
		 * Method to start the display of a progress dialog.
		 * @param msg the message to show in the progress dialog.
		 */
		void startProgressDialog(String msg);

		/**
		 * Method to stop the progress bar
		 */
		void stopProgressDialog();

		/**
		 * Method to set a text message in the progress dialog.
		 * @param message the new progress message.
		 */
		void setProgressNote(String message);

		/**
		 * Method to update the progress bar
		 * @param done the amount done (from 0 to total-1).
		 * @param total the total amount to do.
		 */
		void setProgressValue(long done, long total);

		/**
		 * Method to instantiate RouteResolution.
		 * Can be called from any thread.
		 * @param resolution RouteResolution
		 */
		void instantiate(RouteResolution resolution);

        /**
		 * flush changes
		 * Can be called only from database thread
		 * @param force unconditionally perform the final flush
		 */
		void flush(boolean force);
	}

	/************************************** ROUTEBATCH: A COLLECTION OF NEEDEDROUTE OBJECTS ON THE SAME NETWORK **************************************/

	/**
	 * Class to hold a "batch" of NeededRoute objects, all on the same network.
	 */
	private class RouteBatch implements Comparable<RouteBatch>
	{
		Set<ArcInst> unroutedArcs;
		Set<NodeInst> unroutedNodes;
		List<NeededRoute> routesInBatch;
		boolean isPwrGnd;
		double length;
		private RouteResolution resolution;
		private final ReentrantLock completedRouteLock = new ReentrantLock();

		public RouteBatch()
		{
			unroutedArcs = new HashSet<ArcInst>();
			unroutedNodes = new HashSet<NodeInst>();
			routesInBatch = new ArrayList<NeededRoute>();
			CellId destCellId = cell.getId();
			resolution = new RouteResolution(destCellId);
			isPwrGnd = false;
			length = 0;
		}

		public void addRoute(NeededRoute nr)
		{
			routesInBatch.add(nr);
		}

		private void completedRoute(NeededRoute nr, Wavefront winningWF)
		{
			completedRouteLock.lock();
			try {
				if (winningWF != null && winningWF.vertices != null) {
					// if route was successful, setup geometry for it
					winningWF.createRoute();
				}

				if (unroutedArcs.size() > 0 || unroutedNodes.size() > 0)
				{
					// remove the original unrouted nodes/arcs
					for (ArcInst aiKill : unroutedArcs) resolution.killArc(aiKill);
					for (NodeInst niKill : unroutedNodes) resolution.killNode(niKill);
					unroutedArcs.clear();
					unroutedNodes.clear();
				}
				if (!DEBUGGRIDDING)
				{
					handler.instantiate(resolution);

					// schedule routing of any taps on a spine
					if (nr.spineTaps != null)
					{
						handler.flush(true);
						int tapNumber = 1;
						for(SearchVertex sv : nr.spineTapMap.keySet())
						{
							PortInst aPi = nr.spineTapMap.get(sv);
							ArcProto aArc = getMetalArcOnPort(aPi);
							ImmutableNodeInst ini = nr.spineTapNIMap.get(aPi);
							if (ini != null)
							{
								NodeInst ni = cell.getNodeById(ini.nodeId);
								if (ni != null)
								{
									// schedule the route to make the tap
									PortInst bPi = ni.getOnlyPortInst();
									ArcProto bArc = getMetalArcOnPort(bPi);
									if (aArc != null && bArc != null)
									{
										String routeName = nr.routeName;
										if (routeName.endsWith("(spine)"))
											routeName = routeName.substring(0, routeName.length()-1) + " tap " + tapNumber + ")";
										NeededRoute nrTap = new NeededRoute(routeName, aPi, bPi, aArc, bArc, null, nr.minWidth);
										if (inValidPort(aPi, nrTap) || inValidPort(bPi, nrTap)) continue;
										nrTap.setNetID(nr.netID);
										nrTap.growNetwork();
										tapRoutes.add(nrTap);
										nrTap.setBatchInfo(nr.batch, tapNumber++);
									}
								}
							}
						}
					}
				}
			} finally {
				completedRouteLock.unlock();
			}
		}

		/**
		 * Method to sort RouteBatch by their length and power/ground usage.
		 */
		public int compareTo(RouteBatch other)
		{
			// make power or ground nets come first
			if (isPwrGnd != other.isPwrGnd)
			{
				if (isPwrGnd) return -1;
				return 1;
			}

			// make shorter nets come before longer ones
			if (length < other.length) return -1;
			if (length > other.length) return 1;
			return 0;
		}
	}

	/************************************** NEEDEDROUTE: A ROUTE TO BE RUN **************************************/

	Map<Integer,List<MutableInteger>> netIDsByValue = new HashMap<Integer,List<MutableInteger>>();

	/**
	 * Class to hold a route that must be run.
	 */
	public class NeededRoute
	{
		private String routeName;
		private RouteBatch batch;
		private int routeInBatch;
		private final Rectangle2D routeBounds;
		private MutableInteger netID;
		private final double minWidth;
		private final Rectangle2D jumpBound;
		private int complexityLimit;
		private double aX, aY, bX, bY;
		private FixpRectangle aRect, bRect;
		private FixpRectangle aRectGridded, bRectGridded;
		private final int aZ, bZ;
		private final PortInst aPi, bPi;
		private final List<PortInst> spineTaps;
		private final Map<SearchVertex,PortInst> spineTapMap;
		private final Map<PortInst,ImmutableNodeInst> spineTapNIMap;
		private final Poly aPoly, bPoly;
		private Rectangle2D[] buckets;
		private Map<Layer,List<SOGBound>> endBlockages;
		/** true when routed successfully */ private volatile boolean routedSuccess;
		private String errorMessage;
		/** true when this is the second try on the route */	private boolean reroute;
		private double[][] gridLocationsX, gridLocationsY;
		private Map<SOGBound,Integer> extractList;
		private boolean[] overridePreventArcs;
		private double[] overrideMetalWidth, overrideMetalSpacingX, overrideMetalSpacingY;

		public NeededRoute(String routeName, PortInst aPi, PortInst bPi, ArcProto aArc, ArcProto bArc, List<PortInst> spineTaps, double minWidth)
		{
			// determine the coordinates of the route
			this.routeName = routeName;
			this.minWidth = minWidth;
			this.spineTaps = spineTaps;
			this.complexityLimit = prefs.complexityLimit;
			if (spineTaps == null)
			{
				spineTapMap = null;
				spineTapNIMap = null;
			} else
			{
				spineTapMap =  new HashMap<SearchVertex,PortInst>();
				spineTapNIMap = new HashMap<PortInst,ImmutableNodeInst>();
			}
			overrideMetalWidth = null;
			overrideMetalSpacingX = overrideMetalSpacingY = null;
			for(int z=0; z<numMetalLayers; z++)
			{
				Double overrideWidth = sogp.getDefaultWidthOverride(metalArcs[z]);
				SeaOfGatesArcProperties sogap = sogp.getOverridesForArcsOnNet(routeName, metalArcs[z]);
				if (sogap != null && sogap.getWidthOverride() != null) overrideWidth = sogap.getWidthOverride();
				if (overrideWidth != null)
				{
					if (overrideMetalWidth == null)
					{
						overrideMetalWidth = new double[numMetalLayers];
						for (int i = 0; i < numMetalLayers; i++)
							overrideMetalWidth[i] = Math.max(metalArcs[i].getDefaultLambdaBaseWidth(ep), minWidth);
					}
					overrideMetalWidth[z] = overrideWidth.doubleValue();
				}
				Double overrideSpacing = sogp.getDefaultSpacingOverride(metalArcs[z]);
				if (sogap != null && sogap.getSpacingOverride() != null) overrideSpacing = sogap.getSpacingOverride();
				if (overrideSpacing != null)
				{
					if (overrideMetalSpacingX == null)
					{
						overrideMetalSpacingX = new double[numMetalLayers];
						overrideMetalSpacingY = new double[numMetalLayers];
						for (int i = 0; i < numMetalLayers; i++)
						{
							overrideMetalSpacingX[i] = overrideMetalSpacingY[i] = 0;
							DRCTemplate rule = DRC.getSpacingRule(metalLayers[i], null, metalLayers[i], null,
								false, -1, metalArcs[i].getDefaultLambdaBaseWidth(ep), 50);
							if (rule != null)
							{
								overrideMetalSpacingX[i] = rule.getValue(0);
								if (rule.getNumValues() <= 1) overrideMetalSpacingY[i] = overrideMetalSpacingX[i]; else
									overrideMetalSpacingY[i] = rule.getValue(1);
									
							}
						}
					}
					overrideMetalSpacingX[z] = overrideMetalSpacingY[z] = overrideSpacing.doubleValue();
				}
			}

			this.aPi = aPi;
			this.bPi = bPi;
			aPoly = aPi.getPoly();
			bPoly = bPi.getPoly();

			// determine area of endpoints
			aRect = aPoly.getBounds2D();
			bRect = bPoly.getBounds2D();

			if (bRect.getMaxX() < aRect.getMinX())
			{
				bX = upToGrain(bRect.getCenterX());
				aX = downToGrain(aRect.getCenterX());
			} else if (bRect.getMinX() > aRect.getMaxX())
			{
				bX = downToGrain(bRect.getCenterX());
				aX = upToGrain(aRect.getCenterX());
			} else
			{
				double xVal = (Math.max(bRect.getMinX(), aRect.getMinX()) + Math.min(bRect.getMaxX(), aRect.getMaxX())) / 2;
				bX = aX = upToGrain(xVal);
			}
			if (bRect.getMaxY() < aRect.getMinY())
			{
				bY = upToGrain(bRect.getCenterY());
				aY = downToGrain(aRect.getCenterY());
			} else if (bRect.getMinY() > aRect.getMaxY())
			{
				bY = downToGrain(bRect.getCenterY());
				aY = upToGrain(aRect.getCenterY());
			} else
			{
				double yVal = (Math.max(bRect.getMinY(), aRect.getMinY()) + Math.min(bRect.getMaxY(), aRect.getMaxY())) / 2;
				bY = aY = upToGrain(yVal);
			}

			aZ = aArc.getFunction().getLevel() - 1;
			bZ = bArc.getFunction().getLevel() - 1;

			double lowX = Math.min(aRect.getMinX(), bRect.getMinX()), highX = Math.max(aRect.getMaxX(), bRect.getMaxX());
			double lowY = Math.min(aRect.getMinY(), bRect.getMinY()), highY = Math.max(aRect.getMaxY(), bRect.getMaxY());

			// first construct a grid for an immense bound
			double gap = DRC.getWorstSpacingDistance(tech, -1) * 100;
			Rectangle2D testBounds = new Rectangle2D.Double(lowX - gap, lowY - gap, highX - lowX + gap * 2, highY - lowY + gap * 2);
			buildGrids(testBounds);

			// now expand the end bounds to reach grid locations
			double aLX = getLowerXGrid(aZ, aRect.getMinX());
			double aHX = getUpperXGrid(aZ, aRect.getMaxX());
			double aLY = getLowerYGrid(aZ, aRect.getMinY());
			double aHY = getUpperYGrid(aZ, aRect.getMaxY());
			aRectGridded = FixpRectangle.from(new Rectangle2D.Double(aLX, aLY, aHX-aLX, aHY-aLY));
			double bLX = getLowerXGrid(bZ, bRect.getMinX());
			double bHX = getUpperXGrid(bZ, bRect.getMaxX());
			double bLY = getLowerYGrid(bZ, bRect.getMinY());
			double bHY = getUpperYGrid(bZ, bRect.getMaxY());
			bRectGridded = FixpRectangle.from(new Rectangle2D.Double(bLX, bLY, bHX-bLX, bHY-bLY));

			// now define bounds as the gridded envelope of a smaller bound
			double maxStrayFromRouteBoundsX = DRC.getWorstSpacingDistance(tech, -1) * 2;
			double maxStrayFromRouteBoundsY = maxStrayFromRouteBoundsX;
			double griddedLowX = Math.min(getLowerXGrid(aZ, lowX-maxStrayFromRouteBoundsX), getLowerXGrid(bZ, lowX-maxStrayFromRouteBoundsX));
			double griddedHighX = Math.max(getUpperXGrid(aZ, highX+maxStrayFromRouteBoundsX), getUpperXGrid(bZ, highX+maxStrayFromRouteBoundsX));
			double griddedLowY = Math.min(getLowerYGrid(aZ, lowY-maxStrayFromRouteBoundsY), getLowerYGrid(bZ, lowY-maxStrayFromRouteBoundsY));
			double griddedHighY = Math.max(getUpperYGrid(aZ, highY+maxStrayFromRouteBoundsY), getUpperYGrid(bZ, highY+maxStrayFromRouteBoundsY));
			routeBounds = new Rectangle2D.Double(griddedLowX, griddedLowY, griddedHighX - griddedLowX, griddedHighY - griddedLowY);
			jumpBound = new Rectangle2D.Double(Math.min(aX, bX), Math.min(aY, bY), Math.abs(aX-bX), Math.abs(aY-bY));

			// set overrides for this specific route
			overridePreventArcs = null;
			List<ArcProto> arcs = sogp.getArcsOnNet(routeName);
			if (arcs != null && arcs.size() > 0)
			{
				overridePreventArcs = new boolean[numMetalLayers];
				for(int i=0; i<numMetalLayers; i++) overridePreventArcs[i] = true;
				for(ArcProto ap : arcs)
				{
					int metNum = ap.getFunction().getLevel() - 1;
					overridePreventArcs[metNum] = false;
				}
			}
		}

		public double[][] getXRoutingGrid() { return gridLocationsX; }

		public double[][] getYRoutingGrid() { return gridLocationsY; }

		/**
		 * Method to construct grids for all layers in the vicinity of this route.
		 */
		public void buildGrids(Rectangle2D bounds)
		{
			gridLocationsX = new double[numMetalLayers][];
			gridLocationsY = new double[numMetalLayers][];

			// first make the grid positions for each layer
			for(int metNum=1; metNum<=numMetalLayers; metNum++)
			{
				int metIndex = metNum - 1;
				double[] thisGrid = metalGrid[metIndex];
				if (thisGrid == null) continue;
				if (!sogp.isForceHorVer() && !sogp.isFavorHorVer()) continue;
				boolean hor = true;
				if (sogp.isHorizontalEven())
				{
					if ((metNum%2) != 0) hor = false;
				} else
				{
					if ((metNum%2) == 0) hor = false;
				}
				double offset = thisGrid[0];
				double range = thisGrid[thisGrid.length-1] - offset;
				if (range > 0)
				{
					List<Double> values = new ArrayList<Double>();
					double low, high;
					if (hor)
					{
						low = bounds.getMinY();
						high = bounds.getMaxY();
					} else
					{
						low = bounds.getMinX();
						high = bounds.getMaxX();
					}
					double lowGroup = Math.floor((low - offset) / range) * range;
					double highGroup = Math.ceil((high - offset) / range) * range;
					for(double v = lowGroup; v <= highGroup; v += range)
					{
						for(int i=0; i<thisGrid.length; i++)
						{
							double val = v + thisGrid[i];
							if (val >= low && val <= high)
								values.add(new Double(val));
						}
					}
					if (values.size() >= 2)
					{
						if (hor)
						{
							gridLocationsY[metIndex] = new double[values.size()];
							for(int i=0; i<values.size(); i++) gridLocationsY[metIndex][i] = values.get(i).doubleValue();
						} else
						{
							gridLocationsX[metIndex] = new double[values.size()];
							for(int i=0; i<values.size(); i++) gridLocationsX[metIndex][i] = values.get(i).doubleValue();
						}
					}
				}
			}

			// now make the intermediate grid positions that combine locations from upper and lower layers
			for(int metNum=1; metNum<=numMetalLayers; metNum++)
			{
				int metIndex = metNum - 1;
				boolean hor = true;
				if (sogp.isHorizontalEven())
				{
					if ((metNum%2) != 0) hor = false;
				} else
				{
					if ((metNum%2) == 0) hor = false;
				}

				Set<Double> values = new TreeSet<Double>();
				if (hor)
				{
					// horizontal layer: combine X locations from upper and lower layers
					if (metIndex > 0 && gridLocationsX[metIndex-1] != null)
					{
						for(int i=0; i<gridLocationsX[metIndex-1].length; i++)
							values.add(new Double(gridLocationsX[metIndex-1][i]));
					}
					if (metIndex < numMetalLayers-1 && gridLocationsX[metIndex+1] != null)
					{
						for(int i=0; i<gridLocationsX[metIndex+1].length; i++)
							values.add(new Double(gridLocationsX[metIndex+1][i]));
					}
					if (values.size() >= 2)
					{
						gridLocationsX[metIndex] = new double[values.size()];
						int i=0;   for(Double v : values) gridLocationsX[metIndex][i++] = v.doubleValue();
					}
				} else
				{
					// vertical layer: combine Y locations from upper and lower layers
					if (metIndex > 0 && gridLocationsY[metIndex-1] != null)
					{
						for(int i=0; i<gridLocationsY[metIndex-1].length; i++)
							values.add(new Double(gridLocationsY[metIndex-1][i]));
					}
					if (metIndex < numMetalLayers-1 && gridLocationsY[metIndex+1] != null)
					{
						for(int i=0; i<gridLocationsY[metIndex+1].length; i++)
							values.add(new Double(gridLocationsY[metIndex+1][i]));
					}
					if (values.size() >= 2)
					{
						gridLocationsY[metIndex] = new double[values.size()];
						int i=0;   for(Double v : values) gridLocationsY[metIndex][i++] = v.doubleValue();
					}
				}
			}
		}

		/**
		 * Method to adjust an X value down to the lower grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the X value to be adjusted.
		 * @return the closest X grid value at or below the given value.
		 */
		public double getLowerXGrid(int metNum, double value)
		{
			return findLowerValue(gridLocationsX[metNum], value);
		}

		/**
		 * Method to adjust an X value up to the higher grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the X value to be adjusted.
		 * @return the closest X grid value at or above the given value.
		 */
		public double getUpperXGrid(int metNum, double value)
		{
			return findUpperValue(gridLocationsX[metNum], value);
		}

		/**
		 * Method to adjust an X value up to the nearest grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the X value to be adjusted.
		 * @return the closest X grid value to the given value.
		 */
		public double getClosestXGrid(int metNum, double value)
		{
			return findClosestValue(gridLocationsX[metNum], value);
		}

		/**
		 * Method to adjust an Y value down to the lower grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the Y value to be adjusted.
		 * @return the closest Y grid value at or below the given value.
		 */
		public double getLowerYGrid(int metNum, double value)
		{
			return findLowerValue(gridLocationsY[metNum], value);
		}

		/**
		 * Method to adjust an Y value up to the higher grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the Y value to be adjusted.
		 * @return the closest Y grid value at or above the given value.
		 */
		public double getUpperYGrid(int metNum, double value)
		{
			return findUpperValue(gridLocationsY[metNum], value);
		}

		/**
		 * Method to adjust an Y value up to the nearest grid value.
		 * @param metNum the metal layer number (0-based).
		 * @param value the Y value to be adjusted.
		 * @return the closest Y grid value to the given value.
		 */
		public double getClosestYGrid(int metNum, double value)
		{
			return findClosestValue(gridLocationsY[metNum], value);
		}

		public boolean isOnXGrid(int metNum, double value)
		{
			return isOnGrid(gridLocationsX[metNum], value);
		}

		public boolean isOnYGrid(int metNum, double value)
		{
			return isOnGrid(gridLocationsY[metNum], value);
		}

		private double findLowerValue(double[] thisGrid, double value)
		{
			if (thisGrid == null) return value;
			int lo = 0, hi = thisGrid.length - 1;
			if (value <= thisGrid[lo]) return value;
			if (value >= thisGrid[hi]) return value;

			for(int i=0; i<1000; i++)
			{
				int med = (hi + lo) / 2;
				if (value >= thisGrid[med] && value < thisGrid[med+1]) return thisGrid[med];
				if (value < thisGrid[med]) hi = med; else lo = med;
			}
			return value;
		}

		private double findUpperValue(double[] thisGrid, double value)
		{
			if (thisGrid == null) return value;

			int lo = 0, hi = thisGrid.length - 1;
			if (value <= thisGrid[lo]) return value;
			if (value >= thisGrid[hi]) return value;

			for(int i=0; i<1000; i++)
			{
				int med = (hi + lo) / 2;
				if (value > thisGrid[med] && value <= thisGrid[med+1]) return thisGrid[med+1];
				if (value <= thisGrid[med]) hi = med; else lo = med;
			}
			return value;
		}

		private double findClosestValue(double[] thisGrid, double value)
		{
			if (thisGrid == null) return value;

			int lo = 0, hi = thisGrid.length - 1;
			if (value <= thisGrid[lo]) return value;
			if (value >= thisGrid[hi]) return value;

			for(int i=0; i<1000; i++)
			{
				int med = (hi + lo) / 2;
				if (value >= thisGrid[med] && value <= thisGrid[med+1])
				{
					if (value - thisGrid[med] < thisGrid[med+1] - value)
						return thisGrid[med];
					return thisGrid[med+1];
				}
				if (value <= thisGrid[med]) hi = med; else lo = med;
			}
			return value;
		}

		private boolean isOnGrid(double[] thisGrid, double value)
		{
			if (thisGrid != null)
			{
				int lo = 0, hi = thisGrid.length - 1;
				if (value < thisGrid[lo]) return false;
				if (value > thisGrid[hi]) return false;

				for(int i=0; i<1000; i++)
				{
					int med = (hi + lo) / 2;
					if (value >= thisGrid[med] && value <= thisGrid[med+1])
					{
						if (value == thisGrid[med] || thisGrid[med+1] == value)
							return true;
						return false;
					}
					if (value <= thisGrid[med]) hi = med; else lo = med;
				}
				return false;
			}
			return true;
		}

		/**
		 * Method to round a value up to the nearest routing grain size.
		 * @param v the value to round up.
		 * @return the granularized value.
		 */
		private double upToGrain(double v)
		{
			return v;
		}

		/**
		 * Method to round a value up to the nearest routing grain size.
		 * @param v the value to round up.
		 * @return the granularized value.
		 */
		private double upToGrainAlways(double v)
		{
			return Math.ceil(v);
		}

		/**
		 * Method to round a value down to the nearest routing grain size.
		 * @param v the value to round down.
		 * @return the granularized value.
		 */
		private double downToGrain(double v)
		{
			return v;
		}

		/**
		 * Method to round a value down to the nearest routing grain size.
		 * @param v the value to round down.
		 * @return the granularized value.
		 */
		private double downToGrainAlways(double v)
		{
			return Math.floor(v);
		}

		public Wavefront[] makeWavefronts()
		{
			// make two wavefronts going in both directions
			Wavefront dirAtoB = new Wavefront(this, aPi, aRect, aX, aY, aZ, OFFSETENDA, bPi, bRect, bRectGridded, bX, bY, bZ, OFFSETENDB, 1, "a->b");
			Wavefront dirBtoA = new Wavefront(this, bPi, bRect, bX, bY, bZ, OFFSETENDB, aPi, aRect, aRectGridded, aX, aY, aZ, OFFSETENDA, -1, "b->a");

//			if (ANYPOINTONDESTINATION)
//			{
//				// mark the blockages with the two ends of the route
//				growPoint(aX, aY, aZ, netID+OFFSETENDA, true);
//				growPoint(bX, bY, bZ, netID+OFFSETENDB, true);
//			}
			return new Wavefront[] { dirAtoB, dirBtoA };
		}

		public void setBatchInfo(RouteBatch batch, int routeInBatch)
		{
			this.batch = batch;
			this.routeInBatch = routeInBatch;
		}

		public int getNumInBatch() { return batch.routesInBatch.size(); }

		public int getRouteInBatch() { return routeInBatch; }

		public MutableInteger getNetID() { return netID; }

		public void setNetID(MutableInteger id) { netID = id; }

		private void setNetID(Network net)
		{
			Integer netIDI = netIDs.get(net);
			assert netIDI != null;
			netID = new MutableInteger(netIDI.intValue());

			// keep track of all MutableIntegers by netID value
			List<MutableInteger> theseNetIDs = netIDsByValue.get(netIDI);
			if (theseNetIDs == null) netIDsByValue.put(netIDI, theseNetIDs = new ArrayList<MutableInteger>());
			theseNetIDs.add(netID);
		}

		public double getArcWidth(int metNum)
		{
			if (overrideMetalWidth != null) return overrideMetalWidth[metNum];
			double width = Math.max(metalArcs[metNum].getDefaultLambdaBaseWidth(ep), minWidth);
			return width;
		}

		/**
		 * Method to determine the design rule spacing between two pieces of a given layer.
		 * @param layer the layer index.
		 * @param width the width of one of the pieces (-1 to use default).
		 * @param length the length of one of the pieces (-1 to use default).
		 * @return the design rule spacing (0 if none).
		 */
		public double[] getSpacingRule(int layer, double width, double length)
		{
			// use override spacing if specified
			if (overrideMetalSpacingX != null) return new double[] {overrideMetalSpacingX[layer], overrideMetalSpacingY[layer]};

			// use default width if none specified
			if (width < 0) width = metalArcs[layer].getDefaultLambdaBaseWidth(ep);
			if (length < 0) length = 50;

			// see if the rule is cached
			Double wid = Double.valueOf(width);
			Map<Double, double[]> widMap = layerSurround[layer].get(wid);
			if (widMap == null)
			{
				synchronized(layerSurround)
				{
					widMap = layerSurround[layer].get(wid);
					if (widMap == null)
						layerSurround[layer].put(wid, widMap = new HashMap<Double, double[]>());
				}
			}
			Double len = Double.valueOf(length);
			double[] value = widMap.get(len);
			if (value == null)
			{
				// rule not cached: compute it
				Layer lay = metalLayers[layer];
				DRCTemplate rule = DRC.getSpacingRule(lay, null, lay, null, false, -1, width, length);
				double x = 0, y = 0;
				if (rule != null)
				{
					x = rule.getValue(0);
					if (rule.getNumValues() <= 1) y = x; else
						y = rule.getValue(1);
				}
				value = new double[] {x, y};
				widMap.put(len, value);
			}
			return value;
		}

		/**
		 * Method to tell whether a given layer of metal can be used in the route.
		 * @param metNum the metal number (starting at 1).
		 * @return true to disallow use of that metal in the route.
		 */
		public boolean preventArc(int metNum)
		{
			if (overridePreventArcs != null) return overridePreventArcs[metNum];
			return preventArcs[metNum];
		}

		public String getName() { return routeName; }

		/**
		 * Method to return the bounds of this route.
		 * No geometry may be placed outside of this area.
		 * @return the bounds of this route.
		 */
		public Rectangle2D getBounds() { return routeBounds; }

		/**
		 * Method to return the PortInst on end A of this NeededRoute.
		 * @return the PortInst on end A of this NeededRoute.
		 */
		public PortInst getAPort() { return aPi; }

		/**
		 * Method to return the PortInst on end B of this NeededRoute.
		 * @return the PortInst on end B of this NeededRoute.
		 */
		public PortInst getBPort() { return bPi; }

		/**
		 * Method to return the X coordinate of point A of this NeededRoute.
		 * @return the X coordinate of point A of this NeededRoute.
		 */
		public double getAX() { return aX; }

		/**
		 * Method to return the Y coordinate of point A of this NeededRoute.
		 * @return the Y coordinate of point A of this NeededRoute.
		 */
		public double getAY() { return aY; }

		/**
		 * Method to return the X coordinate of point B of this NeededRoute.
		 * @return the X coordinate of point B of this NeededRoute.
		 */
		public double getBX() { return bX; }

		/**
		 * Method to return the Y coordinate of point B of this NeededRoute.
		 * @return the Y coordinate of point B of this NeededRoute.
		 */
		public double getBY() { return bY; }

		/**
		 * Method to return an R-Tree of blockages on a given via Layer.
		 * @param lay the via Layer to examine.
		 * @return an RTNode that is the top of the tree of blockages on that Layer.
		 */
		public RTNode<SOGBound> getViaTree(Layer lay) { return rTrees.getViaTree(lay).getRoot(); }

		public Iterator<SOGBound> searchViaTree(Layer lay, Rectangle2D bound) {
			return rTrees.getViaTree(lay).search(bound);
		}

		public Rectangle2D[] getGRBuckets() { return buckets; }

		public boolean checkEndSurround()
		{
			// determine "A" end surround
			double fromMetalSpacing = getArcWidth(aZ) / 2;
			double[] fromSurround = getSpacingRule(aZ, metalArcs[aZ].getDefaultLambdaBaseWidth(ep), -1);

			// see if "A" end access is blocked
			SOGBound block = getMetalBlockage(netID, aZ, fromMetalSpacing, fromMetalSpacing, fromSurround, aX, aY);
			if (block != null)
			{
				// see if gridding caused the blockage
				block = getMetalBlockage(netID, aZ, fromMetalSpacing, fromMetalSpacing, fromSurround, aX, aY);
				if (block != null)
				{
					// ungridded center location still blocked: see if port has nonzero area and other places in it are free
					Rectangle2D fromRect = aPoly.getBounds2D();
					double stepSize = fromMetalSpacing + Math.max(fromSurround[0], fromSurround[1]);
					if (stepSize > 0 && (fromRect.getWidth() > 0 || fromRect.getHeight() > 0))
					{
						for(double x = fromRect.getMinX(); x <= fromRect.getMaxX(); x += stepSize)
						{
							for(double y = fromRect.getMinY(); y <= fromRect.getMaxY(); y += stepSize)
							{
								SOGBound stepBlock = getMetalBlockage(netID, aZ, fromMetalSpacing, fromMetalSpacing, fromSurround, x, y);
								if (stepBlock == null)
								{
									aX = x;   aY = y;
									block = null;
									break;
								}
							}
							if (block == null) break;
						}
					}
					if (block != null)
					{
						String errorMsg = "Cannot route from port " + aPi.getPortProto().getName()
							+ " of node " + describe(aPi.getNodeInst()) + " at ("
							+ formatDistance(aX) + "," + formatDistance(aY)
							+ ") because it is blocked on layer " + metalLayers[aZ].getName()
							+ " [needs " + formatDistance(fromMetalSpacing + Math.max(fromSurround[0], fromSurround[1]))
							+ " all around, blockage is "
							+ formatDistance(block.getBounds().getMinX()) + "<=X<="
							+ formatDistance(block.getBounds().getMaxX()) + " and "
							+ formatDistance(block.getBounds().getMinY()) + "<=Y<="
							+ formatDistance(block.getBounds().getMaxY())
//							+ ", block on net " + block.getNetID() + " but this net is " + this.netID
							+ "]";
						if (reroute) errorMsg = "(Retry) " + errorMsg;
						error(errorMsg);
						List<PolyBase> polyList = new ArrayList<PolyBase>();
						polyList.add(new PolyBase(aX, aY, (fromMetalSpacing + fromSurround[0]) * 2,
							(fromMetalSpacing + fromSurround[1]) * 2));
						polyList.add(new PolyBase(block.getBounds()));
						List<EPoint> lineList = new ArrayList<EPoint>();
						lineList.add(EPoint.fromLambda(block.getBounds().getMinX(), block.getBounds().getMinY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMaxX(), block.getBounds().getMaxY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMinX(), block.getBounds().getMaxY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMaxX(), block.getBounds().getMinY()));
						errorLogger.logMessageWithLines(errorMsg, polyList, lineList, cell, 0, true);
						return true;
					}
				}
			}

			// determine "B" end surround
			double toMetalSpacing = getArcWidth(bZ) / 2;
			double[] toSurround = getSpacingRule(bZ, metalArcs[bZ].getDefaultLambdaBaseWidth(ep), -1);

			// see if "B" end access is blocked
			block = getMetalBlockage(netID, bZ, toMetalSpacing, toMetalSpacing, toSurround, bX, bY);
			if (block != null)
			{
				// see if gridding caused the blockage
				block = getMetalBlockage(netID, bZ, toMetalSpacing, toMetalSpacing, toSurround, bX, bY);
				if (block != null)
				{
					// ungridded center location still blocked: see if port has nonzero area and other places in it are free
					Rectangle2D toRect = bPoly.getBounds2D();
					double stepSize = toMetalSpacing + Math.max(toSurround[0], toSurround[1]);
					if (stepSize > 0 && (toRect.getWidth() > 0 || toRect.getHeight() > 0))
					{
						for(double x = toRect.getMinX(); x <= toRect.getMaxX(); x += stepSize)
						{
							for(double y = toRect.getMinY(); y <= toRect.getMaxY(); y += stepSize)
							{
								SOGBound stepBlock = getMetalBlockage(netID, bZ, toMetalSpacing, toMetalSpacing, toSurround, x, y);
								if (stepBlock == null)
								{
									bX = x;   bY = y;
									block = null;
									break;
								}
							}
							if (block == null) break;
						}
					}
					if (block != null)
					{
						String errorMsg = "Cannot route to port " + bPi.getPortProto().getName()
							+ " of node " + describe(bPi.getNodeInst()) + " at ("
							+ formatDistance(bX) + "," + formatDistance(bY)
							+ ") because it is blocked on layer " + metalLayers[bZ].getName()
							+ " [needs " + formatDistance(toMetalSpacing + Math.max(toSurround[0], toSurround[1]))
							+ " all around, blockage is "
							+ formatDistance(block.getBounds().getMinX()) + "<=X<="
							+ formatDistance(block.getBounds().getMaxX()) + " and "
							+ formatDistance(block.getBounds().getMinY()) + "<=Y<="
							+ formatDistance(block.getBounds().getMaxY())
//							+ ", block on net " + block.getNetID() + " but this net is " + this.netID
							+ "]";
						if (reroute) errorMsg = "(Retry) " + errorMsg;
						error(errorMsg);
						List<PolyBase> polyList = new ArrayList<PolyBase>();
						polyList.add(new PolyBase(bX, bY, (toMetalSpacing + toSurround[0]) * 2,
							(toMetalSpacing + toSurround[1]) * 2));
						polyList.add(new PolyBase(block.getBounds()));
						List<EPoint> lineList = new ArrayList<EPoint>();
						lineList.add(EPoint.fromLambda(block.getBounds().getMinX(), block.getBounds().getMinY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMaxX(), block.getBounds().getMaxY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMinX(), block.getBounds().getMaxY()));
						lineList.add(EPoint.fromLambda(block.getBounds().getMaxX(), block.getBounds().getMinY()));
						errorLogger.logMessageWithLines(errorMsg, polyList, lineList, cell, 0, true);
						return true;
					}
				}
			}
			return false;
		}

		private void growNetwork()
		{
			// initialize list of SOGBounds to extract
			extractList = new HashMap<SOGBound,Integer>();

			// fill list from the two endpoints of the route
			growPoint(aX, aY, aZ, netID);
			growPoint(bX, bY, bZ, netID);

			// add in blockage setting for any tap points on the spine
			if (spineTaps != null)
			{
				for(PortInst pi : spineTaps)
				{
					ArcProto ap = getMetalArcOnPort(pi);
					if (ap == null) continue;
					int z = ap.getFunction().getLevel() - 1;
					EPoint pt = pi.getCenter();
					growPoint(pt.getX(), pt.getY(), z, netID);
				}
			}

			// iterate until the list is empty
			for(;;)
			{
				Iterator<SOGBound> it = extractList.keySet().iterator();
				if (!it.hasNext()) break;
				SOGBound sBound = it.next();
				Integer layerNumInt = extractList.get(sBound);
				extractList.remove(sBound);
				growArea(sBound.bound, layerNumInt.intValue(), sBound.getNetID());
			}
			extractList = null;
		}

		private void growPoint(double x, double y, int layerNum, MutableInteger idNumber)
		{
			Rectangle2D search = new Rectangle2D.Double(x, y, 0, 0);
			BlockageTree bTree = rTrees.getMetalTree(metalLayers[layerNum]);
			if (bTree.isEmpty()) return;
			for (Iterator<SOGBound> sea = bTree.search(search); sea.hasNext();)
			{
				SOGBound sBound = sea.next();
				if (sBound.getNetID() == null)
				{
					sBound.setNetID(idNumber);
					if (extractList.get(sBound) == null)
					{
						extractList.put(sBound, Integer.valueOf(layerNum));
						blockagesFound++;
						if ((blockagesFound%100) == 0)
							setProgressValue(blockagesFound, totalBlockages);
					}
					continue;
				}
				sBound.updateNetID(idNumber, netIDsByValue);
			}
		}

		private void growArea(Rectangle2D bound, int layerNum, MutableInteger idNumber)
		{
			BlockageTree metalTree = rTrees.getMetalTree(metalLayers[layerNum]);
			for (Iterator<SOGBound> sea = metalTree.search(bound); sea.hasNext(); )
			{
				SOGBound sBound = sea.next();
				if (sBound.getNetID() == null)
				{
					sBound.setNetID(idNumber);
					if (extractList.get(sBound) == null)
					{
						extractList.put(sBound, Integer.valueOf(layerNum));
						blockagesFound++;
						if ((blockagesFound%100) == 0)
							setProgressValue(blockagesFound, totalBlockages);
					}
					continue;
				}
				sBound.updateNetID(idNumber, netIDsByValue);
			}

			// look at vias on lower layer
			if (layerNum > 0)
			{
				BlockageTree viaTree = rTrees.getViaTree(viaLayers[layerNum-1]);
				if (!viaTree.isEmpty())
				{
					for (Iterator<SOGBound> sea = viaTree.search(bound); sea.hasNext(); )
					{
						SOGVia sBound = (SOGVia)sea.next();
						if (sBound.getNetID() == null)
						{
							sBound.setNetID(idNumber);
							growPoint(sBound.loc.getX(), sBound.loc.getY(), layerNum-1, idNumber);
							continue;
						}
						sBound.updateNetID(idNumber, netIDsByValue);
					}
				}
			}

			// look at vias on higher layer
			if (layerNum < numMetalLayers-1)
			{
				BlockageTree bTree = rTrees.getViaTree(viaLayers[layerNum]);
				for (Iterator<SOGBound> sea = bTree.search(bound); sea.hasNext();)
				{
					SOGVia sBound = (SOGVia)sea.next();
					if (sBound.getNetID() == null)
					{
						sBound.setNetID(idNumber);
						growPoint(sBound.loc.getX(), sBound.loc.getY(), layerNum+1, idNumber);
						continue;
					}
					sBound.updateNetID(idNumber, netIDsByValue);
				}
			}
		}

		/**
		 * Method to add extra blockage information that corresponds to ends of each route.
		 */
		private void addBlockagesAtPorts(PortInst pi)
		{
			MutableInteger netIDUse = new MutableInteger(netID.intValue() + OFFSETVIRTUAL);
			PolyBase poly = pi.getPoly();
			Rectangle2D portBounds = poly.getBounds2D();
			ArcProto[] poss = getPossibleConnections(pi.getPortProto());
			int lowMetal = -1, highMetal = -1;
			for (int i = 0; i < poss.length; i++)
			{
				if (poss[i].getTechnology() != tech) continue;
				if (!poss[i].getFunction().isMetal()) continue;
				int level = poss[i].getFunction().getLevel();
				if (lowMetal < 0) lowMetal = highMetal = level; else
				{
					lowMetal = Math.min(lowMetal, level);
					highMetal = Math.max(highMetal, level);
				}
			}
			if (lowMetal < 0) return;

			// reserve space on layers above and below
			Map<Layer,List<Rectangle2D>> blockageRects = new HashMap<Layer,List<Rectangle2D>>();
			for (int via = lowMetal - 2; via < highMetal; via++)
			{
				if (via < 0 || via >= numMetalLayers - 1) continue;
				List<MetalVia> mvs = metalVias[via].getVias();
				int upper = mvs.size();
//upper = 1;   // Used to be uncommented, then ends got blocked
				for(int j=0; j<upper; j++)
				{
					MetalVia mv = mvs.get(j);
					PrimitiveNode np = mv.via;
					SizeOffset so = np.getProtoSizeOffset();
					double xOffset = so.getLowXOffset() + so.getHighXOffset();
					double yOffset = so.getLowYOffset() + so.getHighYOffset();
					double wid = Math.max(np.getDefWidth(ep) - xOffset, minWidth) + xOffset;
					double hei = Math.max(np.getDefHeight(ep) - yOffset, minWidth) + yOffset;
					NodeInst dummy = NodeInst.makeDummyInstance(np, ep, EPoint.ORIGIN, wid, hei, Orientation.IDENT);
					PolyBase[] polys = tech.getShapeOfNode(dummy);
					for (int i = 0; i < polys.length; i++)
					{
						PolyBase metalPoly = polys[i];
						Layer layer = metalPoly.getLayer();
						if (!layer.getFunction().isMetal()) continue;
						Rectangle2D metalBounds = metalPoly.getBounds2D();
						Rectangle2D bounds = new Rectangle2D.Double(metalBounds.getMinX() + portBounds.getCenterX(),
							metalBounds.getMinY() + portBounds.getCenterY(), metalBounds.getWidth(), metalBounds.getHeight());

						// only add blockage if there is nothing else present
						if (NEWBLOCKAGE)
						{
							boolean free = true;
							BlockageTree bTree = rTrees.getMetalTree(layer);
							if (!bTree.isEmpty())
							{
								for (Iterator<SOGBound> sea = bTree.search(bounds); sea.hasNext();)
								{
									SOGBound sBound = sea.next();
									int netValue = 0;
									if (sBound.getNetID() != null) netValue = sBound.getNetID().intValue();
									if (netValue != netID.intValue()) continue;
									if (sBound.getBounds().getMinX() > bounds.getMinX() ||
										sBound.getBounds().getMaxX() < bounds.getMaxX() ||
										sBound.getBounds().getMinY() > bounds.getMinY() ||
										sBound.getBounds().getMaxY() < bounds.getMaxY()) continue;
									free = false;
									break;
								}
							}
							if (free)
							{
								List<Rectangle2D> rects = blockageRects.get(layer);
								if (rects == null) blockageRects.put(layer, rects = new ArrayList<Rectangle2D>());
								rects.add(bounds);
							}
						} else
						{
							// old blockage code
							boolean free = true;
							BlockageTree bTree = rTrees.getMetalTree(layer);
							if (!bTree.isEmpty())
							{
								for (Iterator<SOGBound> sea = bTree.search(bounds); sea.hasNext();)
								{
									SOGBound sBound = sea.next();
									if (sBound.getBounds().getMinX() > bounds.getMaxX() ||
										sBound.getBounds().getMaxX() < bounds.getMinX() ||
										sBound.getBounds().getMinY() > bounds.getMaxY() ||
										sBound.getBounds().getMaxY() < bounds.getMinY()) continue;
									if (sBound.isSameBasicNet(netIDUse)) continue;
									free = false;
									break;
								}
							}
							if (free) addRectangle(bounds, layer, netIDUse, false, false);
						}
					}
				}
			}
			if (NEWBLOCKAGE)
			{
				for(Layer layer : blockageRects.keySet())
				{
					List<Rectangle2D> rects = blockageRects.get(layer);
					for(int i=0; i<rects.size(); i++)
					{
						Rectangle2D bound1 = rects.get(i);
						for(int j=0; j<rects.size(); j++)
						{
							if (j == i) continue;
							Rectangle2D bound2 = rects.get(j);
							if (bound1.getMinX() <= bound2.getMinX() && bound1.getMaxX() >= bound2.getMaxX() &&
								bound1.getMinY() <= bound2.getMinY() && bound1.getMaxY() >= bound2.getMaxY())
							{
								// bound2 is smaller and can be eliminated
								rects.remove(j);
								if (i > j) i--;
								j--;
							}
						}
					}
					for(Rectangle2D bounds : rects)
					{
						SOGBound rtn = addRectangle(bounds, layer, netIDUse, false, false);
						if (endBlockages == null)
							endBlockages = new HashMap<Layer,List<SOGBound>>();
						List<SOGBound> blocksOnLayer = endBlockages.get(layer);
						if (blocksOnLayer == null) endBlockages.put(layer, blocksOnLayer = new ArrayList<SOGBound>());
						blocksOnLayer.add(rtn);
					}
				}
			}
		}

		/**
		 * Method to see if a proposed piece of metal has DRC errors (ignoring notches).
		 * @param netID the network ID of the desired metal (blockages on this netID are ignored).
		 * @param metNo the level of the metal.
		 * @param halfWidth half of the width of the metal.
		 * @param halfHeight half of the height of the metal.
		 * @param surround is the maximum possible DRC surround around the metal (index 0 for X, index 1 for Y).
		 * @param x the X coordinate at the center of the metal.
		 * @param y the Y coordinate at the center of the metal.
		 * @return a blocking SOGBound object that is in the area. Returns null if the area is clear.
		 */
		public SOGBound getMetalBlockage(MutableInteger netID, int metNo, double halfWidth, double halfHeight,
			double surround[], double x, double y)
		{
			// get the R-Tree data for the metal layer
			Layer layer = metalLayers[metNo];
			BlockageTree bTree = rTrees.getMetalTree(layer);
			bTree.lock();
			try {
				// compute the area to search
				double lX = x - halfWidth - surround[0], hX = x + halfWidth + surround[0];
				double lY = y - halfHeight - surround[1], hY = y + halfHeight + surround[1];
				Rectangle2D searchArea = new Rectangle2D.Double(lX, lY, hX - lX, hY - lY);

				// see if there is anything in that area
				for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext(); )
				{
					SOGBound sBound = sea.next();
					ERectangle bound = sBound.getBounds();
					if (DBMath.isLessThanOrEqualTo(bound.getMaxX(), lX) ||
						DBMath.isGreaterThanOrEqualTo(bound.getMinX(), hX) ||
						DBMath.isLessThanOrEqualTo(bound.getMaxY(), lY) ||
						DBMath.isGreaterThanOrEqualTo(bound.getMinY(), hY)) continue;

					// ignore if on the same net
					if (sBound.isSameBasicNet(netID)) continue;

					// if this is a polygon, do closer examination
					if (sBound instanceof SOGPoly)
					{
						PolyBase poly = ((SOGPoly) sBound).getPoly();
						if (!poly.contains(searchArea)) continue;
					}
					return sBound;
				}
				return null;
			} finally {
				bTree.unlock();
			}
		}

		/**
		 * Method to find a via blockage in the R-Tree.
		 * @param netID the network ID of the desired space (vias at this point and on this netID are ignored).
		 * @param layer the via layer being examined.
		 * @param halfWidth half of the width of the area to examine.
		 * @param halfHeight half of the height of the area to examine.
		 * @param x the X coordinate at the center of the area to examine.
		 * @param y the Y coordinate at the center of the area to examine.
		 * @return a blocking SOGVia object that is in the area. Returns null if the area is clear.
		 */
		public SOGVia getViaBlockage(MutableInteger netID, Layer layer, double halfWidth, double halfHeight, double x, double y)
		{
			BlockageTree bTree = rTrees.getViaTree(layer);
			bTree.lock();
			try {
				if (bTree.isEmpty()) return null;

				// see if there is anything in that area
				Rectangle2D searchArea = new Rectangle2D.Double(x - halfWidth, y - halfHeight, halfWidth * 2, halfHeight * 2);
				for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext();)
				{
					SOGVia sLoc = (SOGVia)sea.next();
					if (sLoc.isSameBasicNet(netID))
					{
						if (DBMath.areEquals(sLoc.loc.getX(), x) && DBMath.areEquals(sLoc.loc.getY(), y)) continue;
					}
					return sLoc;
				}
				return null;
			} finally {
				bTree.unlock();
			}
		}

		public void completeRoute(SearchVertex result)
		{
			if (result.wf != null)
			{
				result.wf.vertices = new ArrayList<SearchVertex>();
				getOptimizedList(result, result.wf.vertices);
				assert !result.wf.vertices.isEmpty();

				// if this is a spine route, determine where the taps hit this route
				if (spineTaps != null)
				{
					for(PortInst pi : spineTaps)
					{
						EPoint pt = pi.getCenter();
						SearchVertex lastSV = result.wf.vertices.get(0);
						double bestDist = Double.MAX_VALUE;
						Point2D bestLoc = null;
						int bestInsertPos = 0;
						for (int i=1; i<result.wf.vertices.size(); i++)
						{
							SearchVertex sv = result.wf.vertices.get(i);
							if (lastSV.getZ() == sv.getZ())
							{
								Point2D loc = GenMath.closestPointToSegment(new Point2D.Double(lastSV.getX(), lastSV.getY()),
									new Point2D.Double(sv.getX(), sv.getY()), pt);
								double dist = Math.abs(loc.getX() - pt.getX()) +  Math.abs(loc.getY() - pt.getY());
								if (dist < bestDist)
								{
									bestDist = dist;
									bestLoc = loc;
									bestInsertPos = i;
								}
							}
							lastSV = sv;
						}
						if (bestLoc != null)
						{
							SearchVertex svTap = new SearchVertex(bestLoc.getX(), bestLoc.getY(), result.wf.vertices.get(bestInsertPos).getZ(),
								0, null, null, 0, null);
							result.wf.vertices.add(bestInsertPos, svTap);
							spineTapMap.put(svTap, pi);
						}
					}
				}
				routedSuccess = true;
			} else if (result != svAbandoned)
			{
				// failed to route
				if (result == svLimited)
				{
					errorMessage = "Search for '" + routeName + "' too complex (took more than " + complexityLimit + " steps)";
				} else if (result == svExhausted)
				{
					errorMessage = "Search for '" + routeName + "' examined all possibilities without success";
				} else
				{
					assert result == svAborted;
					errorMessage = "Search for '" + routeName + "' aborted by user";
				}
				if (reroute) errorMessage = "(Retry) " + errorMessage;
				error(errorMessage);
				if (result == svLimited || result == svExhausted)
				{
					List<EPoint> lineList = new ArrayList<EPoint>();
					lineList.add(EPoint.fromLambda(aX, aY));
					lineList.add(EPoint.fromLambda(bX, bY));
//					lineList.add(EPoint.fromLambda(routeBounds.getMinX(), routeBounds.getMinY()));
//					lineList.add(EPoint.fromLambda(routeBounds.getMinX(), routeBounds.getMaxY()));
//					lineList.add(EPoint.fromLambda(routeBounds.getMaxX(), routeBounds.getMaxY()));
//					lineList.add(EPoint.fromLambda(routeBounds.getMaxX(), routeBounds.getMinY()));
					errorLogger.logMessageWithLines(errorMessage, null, lineList, cell, 0, true);
				}
			}
			batch.completedRoute(this, result.wf);
		}
	}

	public static class RouteResolution implements Serializable
	{
		final CellId cellId;
		final List<RouteNode> nodesToRoute = new ArrayList<RouteNode>();
		final List<RouteArc> arcsToRoute = new ArrayList<RouteArc>();
		final List<Integer> nodesIDsToKill = new ArrayList<Integer>();
		final List<Integer> arcsIDsToKill = new ArrayList<Integer>();
		final List<RouteAddUnrouted> unroutedToAdd = new ArrayList<RouteAddUnrouted>();

		public RouteResolution(CellId cellId)
		{
			this.cellId = cellId;
		}

		public void addNode(RouteNode rn) { nodesToRoute.add(rn); }

		public void addArc(RouteArc ra) { arcsToRoute.add(ra); }

		public void killNode(NodeInst ni) { nodesIDsToKill.add(Integer.valueOf(ni.getNodeId())); }

		public void killArc(ArcInst ai) { arcsIDsToKill.add(Integer.valueOf(ai.getArcId())); }

		public void addUnrouted(PortInst piA, PortInst piB) { unroutedToAdd.add(new RouteAddUnrouted(piA, piB)); }

		public void clearRoutes()
		{
	        nodesToRoute.clear();
	        arcsToRoute.clear();
	        nodesIDsToKill.clear();
	        arcsIDsToKill.clear();
	        unroutedToAdd.clear();
		}
	}

	public static class RouteAddUnrouted implements Serializable
	{
		private int nodeIDA, nodeIDB;
		private PortProtoId portIdA, portIdB;
		private EPoint locA, locB;

		public RouteAddUnrouted(PortInst piA, PortInst piB)
		{
			nodeIDA = piA.getNodeInst().getNodeId();
			portIdA = piA.getPortProto().getId();
			locA = piA.getCenter();
			nodeIDB = piB.getNodeInst().getNodeId();
			portIdB = piB.getPortProto().getId();
			locB = piB.getCenter();
		}

		int getTailId() { return nodeIDA; }

		PortProtoId getTailPortProtoId() { return portIdA; }

		EPoint getTailLocation() { return locA; }

		int getHeadId() { return nodeIDB; }

		PortProtoId getHeadPortProtoId() { return portIdB; }

		EPoint getHeadLocation() { return locB; }
	}

	public static class RouteNode implements Serializable
	{
		private boolean exists;
		private NodeProto np;
		private EPoint loc;
		private FixpRectangle rect;
		private double wid;
		private double hei;
		private Orientation orient;
		private int terminalNodeID;
		private PortProtoId terminalNodePort;
		private PortInst tapConnection;
		private NeededRoute nr;

		public RouteNode(NodeProto np, SeaOfGatesEngine soge, EPoint loc, double wid, double hei, Orientation orient, PortInst tapConnection, NeededRoute nr)
		{
			exists = false;
			this.np = np;
			this.loc = loc;
	        long x = FixpCoord.lambdaToFixp(loc.getX());
	        long y = FixpCoord.lambdaToFixp(loc.getY());
			rect = FixpRectangle.fromFixpDiagonal(x, y, x, y);
			this.wid = wid;
			this.hei = hei;
			this.orient = orient;
			this.tapConnection = tapConnection;
			this.nr = nr;

			NodeInst ni = NodeInst.makeDummyInstance(np, soge.ep, loc, wid, hei, orient);
			FixpTransform trans = ni.rotateOut();
			Poly[] nodeInstPolyList = np.getTechnology().getShapeOfNode(ni, true, false, null);
			for (int i = 0; i < nodeInstPolyList.length; i++)
			{
				PolyBase poly = nodeInstPolyList[i];
				if (poly.getPort() == null) continue;
				poly.transform(trans);
				poly.setStyle(Poly.Type.FILLED);
				soge.addLayer(poly, GenMath.MATID, nr.getNetID(), true, null, false);
			}
		}

		public RouteNode(PortInst pi)
		{
			exists = true;
			terminalNodeID = pi.getNodeInst().getNodeId();
			terminalNodePort = pi.getPortProto().getId();
			loc = pi.getCenter();
			rect = pi.getPoly().getBounds2D();
		}

		boolean exists() { return exists; }

		NodeProtoId getProtoId() { return np.getId(); }

		PortInst getTapConnection() { return tapConnection; }

		void setTapConnection(ImmutableNodeInst ini)
		{
			if (tapConnection != null)
				nr.spineTapNIMap.put(tapConnection, ini);
		}

		Name getBaseName() {
			assert !exists;
			PrimitiveNode pn = (PrimitiveNode)np;
			return pn.getPrimitiveFunction(getTechBits()).getBasename();
		}

		Orientation getOrient() { return orient; }

		EPoint getLoc() { return loc; }

		EPoint getSize() {
			if (np instanceof Cell) {
				return EPoint.ORIGIN;
			}
			PrimitiveNode pn = (PrimitiveNode)np;
			ERectangle fullRectangle = pn.getFullRectangle();
			long sizeX = DBMath.lambdaToSizeGrid(wid) - fullRectangle.getGridWidth();
			long sizeY = DBMath.lambdaToSizeGrid(hei) - fullRectangle.getGridHeight();
			return EPoint.fromGrid(sizeX, sizeY);
		}

		int getTechBits() { return 0; }

		int getNodeId() {
			assert exists;
			return terminalNodeID;
		}

		PortProtoId getPortProtoId() {
			if (exists) {
				return terminalNodePort;
			}
			PrimitiveNode pn = (PrimitiveNode)np;
			assert pn.getNumPorts() == 1;
			return pn.getPort(0).getId();
		}
	}

	public class RouteArc implements Serializable
	{
		private ArcProto type;
		private double wid;
		private RouteNode from, to;
		private String netName;

		public RouteArc(ArcProto type, String netName, SeaOfGatesEngine soge, Layer layer, double wid, RouteNode from, RouteNode to, NeededRoute nr)
		{
			this.type = type;
			this.netName = netName;
			this.wid = wid;
			this.from = from;
			this.to = to;

			// presuming a simple arc shape
			EPoint fromLoc = from.loc;
			EPoint toLoc = to.loc;
			totalWireLength += fromLoc.distance(toLoc);
			Poly poly = null;
			if (fromLoc.getX() == toLoc.getX())
			{
				poly = new Poly(fromLoc.getX(), (fromLoc.getY() + toLoc.getY()) / 2,
					wid, Math.abs(fromLoc.getY() - toLoc.getY()) + wid);
			} else if (fromLoc.getY() == toLoc.getY())
			{
				poly = new Poly((fromLoc.getX() + toLoc.getX()) / 2, fromLoc.getY(),
					Math.abs(fromLoc.getX() - toLoc.getX()) + wid, wid);
			} else
			{
				if (from.rect.getMaxX() >= to.rect.getMinX() && from.rect.getMinX() <= to.rect.getMaxX())
				{
					// X coordinates overlap: make vertical arc
					double x = (Math.max(from.rect.getMinX(), to.rect.getMinX()) + Math.min(from.rect.getMaxX(), to.rect.getMaxX())) / 2;
					if (fromLoc.getX() != x) from.loc = EPoint.fromLambda(x, from.loc.getY());
					if (toLoc.getX() != x) to.loc = EPoint.fromLambda(x, to.loc.getY());
					poly = new Poly(x, (fromLoc.getY() + toLoc.getY()) / 2,
						wid, Math.abs(fromLoc.getY() - toLoc.getY()) + wid);
				} else if (from.rect.getMaxY() >= to.rect.getMinY() && from.rect.getMinY() <= to.rect.getMaxY())
				{
					// Y coordinates overlap: make horizontal arc
					double y = (Math.max(from.rect.getMinY(), to.rect.getMinY()) + Math.min(from.rect.getMaxY(), to.rect.getMaxY())) / 2;
					if (fromLoc.getY() != y) from.loc = EPoint.fromLambda(from.loc.getX(), y);
					if (toLoc.getY() != y) to.loc = EPoint.fromLambda(to.loc.getX(), y);
					poly = new Poly((fromLoc.getX() + toLoc.getX()) / 2, y,
						Math.abs(fromLoc.getX() - toLoc.getX()) + wid, wid);
				} else
				{
					String layerName = "";
					if (layer != null) layerName = " " + layer.getName();
					System.out.println("WARNING: angled" + layerName + " wire from (" +
						fromLoc.getX() + "," + fromLoc.getY() + ") to (" + toLoc.getX() + "," + toLoc.getY() + ")");
				}
			}
			if (poly != null && layer != null)
			{
				poly.setLayer(layer);
				poly.setStyle(Poly.Type.FILLED);
				soge.addLayer(poly, GenMath.MATID, nr.getNetID(), true, null, false);
			}

		}

		ArcProtoId getProtoId() {
			return type.getId();
		}

		RouteNode getTail() {
			return to;
		}

		RouteNode getHead() {
			return from;
		}

		String getName() {
			return netName;
		}

		long getGridExtendOverMin() {
			return DBMath.lambdaToGrid(0.5 * wid) - type.getBaseExtend().getGrid();
		}

		int getFlags(EditingPreferences ep) {
			return type.getDefaultInst(ep).flags;
		}
	}

	/************************************** WAVEFRONT: THE ACTUAL SEARCH CODE **************************************/

	/**
	 * Class to define a routing search that advances a "wave" of search coordinates from the starting point
	 * to the ending point.
	 */
	public class Wavefront
	{
		/** The route that this is part of. */							final NeededRoute nr;
		/** Wavefront name (for debugging). */							final String name;
		/** Active search vertices while running wavefront. */			private final OrderedSearchVertex active;
		/** Used search vertices while running wavefront (debug). */	private final List<SearchVertex> inactive;
		/** Resulting list of vertices found for this wavefront. */		List<SearchVertex> vertices;
		/** Set true to abort this wavefront's search. */				volatile boolean abort;
		/** The starting and ending ports of the wavefront. */			final PortInst from, to;
		/** The starting X/Y coordinates of the wavefront. */			final double fromX, fromY;
		/** The starting area of the wavefront. */						final FixpRectangle fromRect;
		/** The starting metal layer of the wavefront. */				final int fromZ;
		/** The ending X/Y coordinates of the wavefront. */				final double toX, toY;
		/** The ending area of the wavefront. */						final FixpRectangle toRect;
		/** The expanded grid of the ending area of the wavefront. */	final FixpRectangle toRectGridded;
		/** The ending metal layer of the wavefront. */					final int toZ;
		/** Count of the number of wavefront advances made. */			int numStepsMade;
		/** Global routing order for this wavefront direction. */		Rectangle2D [] orderedBuckets;
		/** Global routing lowest bucket for each step. */				int [] orderedBase;
		/** Network ID bits for ends of route. */						final int fromBit, toBit;
		/** Direction to move through global routing buckets */			final int globalRoutingDelta;
		@SuppressWarnings({ "unchecked" } )
		/** Search vertices found while running the wavefront. */		final Map<Integer, Map<Integer,SearchVertex>>[] searchVertexPlanes = new Map[numMetalLayers];
		@SuppressWarnings({ "unchecked" } )
		/** true when searching finished successfully or failed */      private boolean finished;
		/** array for optimized vertices (allocated once) */			private List<SearchVertex> optimizedList = new ArrayList<SearchVertex>();

		Wavefront(NeededRoute nr, PortInst from, FixpRectangle fromRect, double fromX, double fromY, int fromZ, int fromBit,
			PortInst to, FixpRectangle toRect, FixpRectangle toRectGridded, double toX, double toY, int toZ, int toBit, int globalRoutingDelta, String name)
		{
			this.nr = nr;
			this.from = from;
			this.fromX = fromX;
			this.fromY = fromY;
			this.fromZ = fromZ;
			this.fromRect = fromRect;
			this.fromBit = fromBit;
			this.to = to;
			this.toX = toX;
			this.toY = toY;
			this.toZ = toZ;
			this.toRect = toRect;
			this.toRectGridded = toRectGridded;
			this.toBit = toBit;
			if (nr.buckets == null) globalRoutingDelta = 0;
			this.globalRoutingDelta = globalRoutingDelta;
			this.name = name;
			this.numStepsMade = 0;
			active = new OrderedSearchVertex();
			inactive = new ArrayList<SearchVertex>();
			vertices = null;
			abort = false;

			SearchVertex svStart = new SearchVertex(fromX, fromY, fromZ, 0, null, null, 0, this);
			if (globalRoutingDelta != 0)
			{
				orderedBuckets = new Rectangle2D[nr.buckets.length];
				orderedBase = new int[nr.buckets.length];
				if (globalRoutingDelta > 0)
				{
					// going from A to B, setup global routing information
					svStart.globalRoutingBucket = 0;
					for(int i=0; i<nr.buckets.length; i++)
					{
						int lastInRun = i;
						for(int j=i+1; j<nr.buckets.length; j++)
						{
							if (nr.buckets[i].getMinX() == nr.buckets[j].getMinX() &&
								nr.buckets[i].getMaxX() == nr.buckets[j].getMaxX()) lastInRun = j;
							if (nr.buckets[i].getMinY() == nr.buckets[j].getMinY()
								&& nr.buckets[i].getMaxY() == nr.buckets[j].getMaxY()) lastInRun = j;
							if (lastInRun != j) break;
						}
						double lX = Math.min(nr.buckets[i].getMinX(), nr.buckets[lastInRun].getMinX());
						double hX = Math.max(nr.buckets[i].getMaxX(), nr.buckets[lastInRun].getMaxX());
						double lY = Math.min(nr.buckets[i].getMinY(), nr.buckets[lastInRun].getMinY());
						double hY = Math.max(nr.buckets[i].getMaxY(), nr.buckets[lastInRun].getMaxY());
						Rectangle2D combinedRun = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);
						int initially = i;   if (i > 0) initially--;
						for(int pos=i; pos<=lastInRun; pos++)
						{
							orderedBuckets[pos] = combinedRun;
							orderedBase[pos] = initially;
							initially = i;
						}
						if (lastInRun == nr.buckets.length-1) break;
						i = lastInRun-1;
					}
				} else
				{
					svStart.globalRoutingBucket = nr.buckets.length-1;
					for(int i = nr.buckets.length-1; i >= 0; i--)
					{
						int lastInRun = i;
						for(int j = i-1; j >= 0; j--)
						{
							if (nr.buckets[i].getMinX() == nr.buckets[j].getMinX() &&
								nr.buckets[i].getMaxX() == nr.buckets[j].getMaxX()) lastInRun = j;
							if (nr.buckets[i].getMinY() == nr.buckets[j].getMinY() &&
								nr.buckets[i].getMaxY() == nr.buckets[j].getMaxY()) lastInRun = j;
							if (lastInRun != j) break;
						}
						double lX = Math.min(nr.buckets[i].getMinX(), nr.buckets[lastInRun].getMinX());
						double hX = Math.max(nr.buckets[i].getMaxX(), nr.buckets[lastInRun].getMaxX());
						double lY = Math.min(nr.buckets[i].getMinY(), nr.buckets[lastInRun].getMinY());
						double hY = Math.max(nr.buckets[i].getMaxY(), nr.buckets[lastInRun].getMaxY());
						Rectangle2D combinedRun = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);
						int initially = i;   if (i < nr.buckets.length-1) initially++;
						for(int pos=i; pos>=lastInRun; pos--)
						{
							orderedBuckets[pos] = combinedRun;
							orderedBase[pos] = initially;
							initially = i;
						}
						if (lastInRun == 0) break;
						i = lastInRun+1;
					}
				}
			}
			svStart.cost = 0;
			setVertex(fromX, fromY, fromZ, svStart);
			active.add(svStart);
		}

		public PortInst getFromPortInst() { return from; }

		public PortInst getToPortInst() { return to; }

		public double getFromX() { return fromX; }

		public double getFromY() { return fromY; }

		public int getFromZ() { return fromZ; }

		public double getToX() { return toX; }

		public double getToY() { return toY; }

		public int getToZ() { return toZ; }

		public Set<SearchVertex> getActive() { return active.getSet(); }

		public List<SearchVertex> getInactive() { return inactive; }

		public NeededRoute getNeededRoute() { return nr; }

		public int getGRDirection() { return globalRoutingDelta; }

		public Rectangle2D[] getOrderedBuckets() { return orderedBuckets; }

		public SearchVertex getNextSearchVertex()
		{
			SearchVertex sv = active.getFirst();
			if (sv == null) return svExhausted;
			return sv;
		}

		/**
		 * Method to tell whether there is a SearchVertex at a given coordinate.
		 * @param x the X coordinate desired.
		 * @param y the Y coordinate desired.
		 * @param z the Z coordinate (metal layer) desired.
		 * @return true if there is a SearchVertex at that point.
		 */
		public SearchVertex getVertex(double x, double y, int z)
		{
			Map<Integer, Map<Integer,SearchVertex>> plane = searchVertexPlanes[z];
			if (plane == null) return null;
			Map<Integer,SearchVertex> row = plane.get(new Integer((int)Math.round(y * DBMath.GRID)));
			if (row == null) return null;
			SearchVertex found = row.get(new Integer((int)Math.round(x * DBMath.GRID)));
			return found;
		}

		/**
		 * Method to mark a given coordinate.
		 * @param x the X coordinate desired.
		 * @param y the Y coordinate desired.
		 * @param z the Z coordinate (metal layer) desired.
		 */
		public void setVertex(double x, double y, int z, SearchVertex sv)
		{
			Map<Integer, Map<Integer,SearchVertex>> plane = searchVertexPlanes[z];
			if (plane == null)
				searchVertexPlanes[z] = plane = new HashMap<Integer, Map<Integer,SearchVertex>>();
			Integer iY = new Integer((int)Math.round(y * DBMath.GRID));
			Map<Integer,SearchVertex> row = plane.get(iY);
			if (row == null)
				plane.put(iY, row = new HashMap<Integer,SearchVertex>());
			row.put(new Integer((int)Math.round(x * DBMath.GRID)), sv);
		}

		public Map<Integer, Map<Integer,SearchVertex>>[] getSearchVertexPlanes() { return searchVertexPlanes; }

		private String[] debugString;

		private void initDebugStrings()
		{
			debugString = new String[7];
		}

		private void setDebugStringHeader(String str)
		{
			debugString[0] = str;
		}

		private void setDebugString(int direction, String str)
		{
			debugString[direction+1] = str;
		}

		private void addDebugString(int direction, String str)
		{
			debugString[direction+1] += str;
		}

		private void completeDebugString(int direction, String str)
		{
			debugString[direction+1] += str;
		}

		/**
		 * Method to advance a wavefront by a single step.
		 * Takes the first SearchVertex in the WaveFront (the one with the lowest cost) and expands it in 6 directions
		 * (+X, -X, +Y, -Y, +Z, -Z), creating up to 6 new SearchVertex objects on the WaveFront.
		 * @return null for a successful advance, non-null to terminate wavefront (could be a completion of the route
		 * or an error).
		 */
		public SearchVertex advanceWavefront()
		{
			// stop if too many steps have been made
			numStepsMade++;
			if (numStepsMade > nr.complexityLimit) return svLimited;

			// get the lowest cost point
			SearchVertex svCurrent = getNextSearchVertex();
			if (svCurrent == svExhausted) return svCurrent;
			active.remove(svCurrent);
			inactive.add(svCurrent);

			double curX = svCurrent.getX();
			double curY = svCurrent.getY();
			int curZ = svCurrent.getZ();

			if (RoutingDebug.isActive())
			{
				initDebugStrings();
				String str = "At: (" + TextUtils.formatDouble(curX) + "," + TextUtils.formatDouble(curY) +
					",M" + (curZ + 1) + "), Cost: " + svCurrent.cost;
				if (globalRoutingDelta == 0) str += ", NO Global Routing"; else
					str += ", Global Routing Bucket: " + svCurrent.globalRoutingBucket;
				setDebugStringHeader(str);
			}

			// see if automatic generation is requested
			int lastDirection = svCurrent.getAutoGen();
			if (lastDirection >= 0)
				svCurrent.generateIntermediateVertex(nr, lastDirection, toRectGridded);

			// look at all directions from this point
			SearchVertex destinationSV = null;
			for (int i = 0; i < 6; i++)
			{
				// compute a neighboring point
				double dx = 0, dy = 0;
				int dz = 0;
				MutableBoolean foundDestination = null;
				boolean stuck = false;
				switch (i)
				{
					case 0:					// move -X
						if (sogp.isForceHorVer() && ((curZ % 2) == 0) == sogp.isHorizontalEven())
						{
							if (RoutingDebug.isActive()) setDebugString(i, "Cannot move in this axis");
							continue;
						}
						if (inDestGrid(toRectGridded, curX-GRAINSIZE, curY)) dx = -GRAINSIZE; else
							dx = nr.getLowerXGrid(curZ, curX-GRAINSIZE) - curX;

						double intermediate = nr.upToGrainAlways(curX + dx);
						if (intermediate != curX + dx) dx = intermediate - curX;

						if (toX - curX < 0)
						{
							// jump as far as possible toward goal
							if (ANYPOINTONDESTINATION) foundDestination = new MutableBoolean(false);
							dx = getJumpSize(svCurrent, curX, curY, curZ, dx, dy, foundDestination);
							if (dx >= 0) { dx = -1;  stuck = true; }
						}
						break;
					case 1:					// move +X
						if (sogp.isForceHorVer() && ((curZ % 2) == 0) == sogp.isHorizontalEven())
						{
							if (RoutingDebug.isActive()) setDebugString(i, "Cannot move in this axis");
							continue;
						}
						if (inDestGrid(toRectGridded, curX+GRAINSIZE, curY)) dx = GRAINSIZE; else
							dx = nr.getUpperXGrid(curZ, curX+GRAINSIZE) - curX;

						intermediate = nr.downToGrainAlways(curX + dx);
						if (intermediate != curX + dx) dx = intermediate - curX;

						if (toX - curX > 0)
						{
							// jump as far as possible toward goal
							if (ANYPOINTONDESTINATION) foundDestination = new MutableBoolean(false);
							dx = getJumpSize(svCurrent, curX, curY, curZ, dx, dy, foundDestination);
							if (dx <= 0) { dx = 1;  stuck = true; }
						}
						break;
					case 2:					// move -Y
						if (sogp.isForceHorVer() && ((curZ % 2) != 0) == sogp.isHorizontalEven())
						{
							if (RoutingDebug.isActive()) setDebugString(i, "Cannot move in this axis");
							continue;
						}
						if (inDestGrid(toRectGridded, curX, curY-GRAINSIZE)) dy = -GRAINSIZE; else
							dy = nr.getLowerYGrid(curZ, curY-GRAINSIZE) - curY;

						intermediate = nr.upToGrainAlways(curY + dy);
						if (intermediate != curY + dy) dy = intermediate - curY;

						if (toY - curY < 0)
						{
							// jump as far as possible toward goal
							if (ANYPOINTONDESTINATION) foundDestination = new MutableBoolean(false);
							dy = getJumpSize(svCurrent, curX, curY, curZ, dx, dy, foundDestination);
							if (dy >= 0) { dy = -1;  stuck = true; }
						}
						break;
					case 3:					// move +Y
						if (sogp.isForceHorVer() && ((curZ % 2) != 0) == sogp.isHorizontalEven())
						{
							if (RoutingDebug.isActive()) setDebugString(i, "Cannot move in this axis");
							continue;
						}
						if (inDestGrid(toRectGridded, curX, curY+GRAINSIZE)) dy = GRAINSIZE; else
							dy = nr.getUpperYGrid(curZ, curY+GRAINSIZE) - curY;

						intermediate = nr.downToGrainAlways(curY + dy);
						if (intermediate != curY + dy) dy = intermediate - curY;

						if (toY - curY > 0)
						{
							// jump as far as possible toward goal
							if (ANYPOINTONDESTINATION) foundDestination = new MutableBoolean(false);
							dy = getJumpSize(svCurrent, curX, curY, curZ, dx, dy, foundDestination);
							if (dy <= 0) { dy = 1;  stuck = true; }
						}
						break;
					case 4:					// move -Z
						dz = -1;
						break;
					case 5:					// move +Z
						dz = 1;
						break;
				}

				if (RoutingDebug.isActive())
				{
					switch (i)
					{
						case 0: setDebugString(i, "Move -" + TextUtils.formatDouble(Math.abs(dx)));  break;
						case 1: setDebugString(i, "Move +" + TextUtils.formatDouble(dx));            break;
						case 2: setDebugString(i, "Move -" + TextUtils.formatDouble(Math.abs(dy)));  break;
						case 3: setDebugString(i, "Move +" + TextUtils.formatDouble(dy));            break;
						case 4: setDebugString(i, "Move -1");                                        break;
						case 5: setDebugString(i, "Move +1");                                        break;
					}
				}
				if (stuck && STICKTOJUMPEDGES)
				{
					if (RoutingDebug.isActive()) completeDebugString(i, ": Cannot Move");
					continue;
				}

				// create the "next" step location
				double nX = curX + dx;
				double nY = curY + dy;
				int nZ = curZ + dz;

				// force next step to be inside of global routing area
				if (foundDestination != null && !foundDestination.booleanValue()) foundDestination = null;
				if (globalRoutingDelta != 0 && foundDestination == null)
				{
					Rectangle2D limit = orderedBuckets[svCurrent.globalRoutingBucket];
					if (nX < limit.getMinX())
					{
						nX = limit.getMinX();
						if (!inDestGrid(toRectGridded, nX, curY)) nX = nr.getUpperXGrid(curZ, nX);
						dx = nX - curX;
						if (dx == 0)
						{
							if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Global Routing X Bounds");
							continue;
						}
					}
					if (nX > limit.getMaxX())
					{
						nX = limit.getMaxX();
						if (!inDestGrid(toRectGridded, nX, curY)) nX = nr.getLowerXGrid(curZ, nX);
						dx = nX - curX;
						if (dx == 0)
						{
							if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Global Routing X Bounds");
							continue;
						}
					}
					if (nY < limit.getMinY())
					{
						nY = limit.getMinY();
						if (!inDestGrid(toRectGridded, curX, nY)) nY = nr.getUpperYGrid(curZ, nY);
						dy = nY - curY;
						if (dy == 0)
						{
							if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Global Routing Y Bounds");
							continue;
						}
					}
					if (nY > limit.getMaxY())
					{
						nY = limit.getMaxY();
						if (!inDestGrid(toRectGridded, curX,nY)) nY = nr.getLowerYGrid(curZ, nY);
						dy = nY - curY;
						if (dy == 0)
						{
							if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Global Routing Y Bounds");
							continue;
						}
					}
				}

				// force next step to be inside of routing bounds
				if (nX < nr.routeBounds.getMinX())
				{
					nX = nr.routeBounds.getMinX();
					if (!inDestGrid(toRectGridded, nX, curY)) nX = nr.getUpperXGrid(curZ, nX);
					dx = nX - curX;
					if (dx == 0)
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of X Bounds");
						continue;
					}
				}
				if (nX > nr.routeBounds.getMaxX())
				{
					nX = nr.routeBounds.getMaxX();
					if (!inDestGrid(toRectGridded, nX, curY)) nX = nr.getLowerXGrid(curZ, nX);
					dx = nX - curX;
					if (dx == 0)
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of X Bounds");
						continue;
					}
				}
				if (nY < nr.routeBounds.getMinY())
				{
					nY = nr.routeBounds.getMinY();
					if (!inDestGrid(toRectGridded, curX, nY)) nY = nr.getUpperYGrid(curZ, nY);
					dy = nY - curY;
					if (dy == 0)
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Y Bounds");
						continue;
					}
				}
				if (nY > nr.routeBounds.getMaxY())
				{
					nY = nr.routeBounds.getMaxY();
					if (!inDestGrid(toRectGridded, curX, nY)) nY = nr.getLowerYGrid(curZ, nY);
					dy = nY - curY;
					if (dy == 0)
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Y Bounds");
						continue;
					}
				}
				if (nZ < 0 || nZ >= numMetalLayers)
				{
					if (RoutingDebug.isActive()) completeDebugString(i, ": Out Of Bounds");
					continue;
				}
				if (nr.preventArc(nZ))
				{
					if (RoutingDebug.isActive()) completeDebugString(i, ": Disallowed Arc");
					continue;
				}

				// see if the point has already been visited
				SearchVertex alreadyThere = getVertex(nX, nY, nZ);
				if (alreadyThere != null)
				{
					if (!active.inList(alreadyThere))
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Already Visited");
						continue;
					}
				}

				// see if the space is available
				int whichContact = 0;
				Point2D[] cuts = null;
				Point2D size = null;
				if (dz == 0)
				{
					// running on one layer: check surround
					double width = nr.getArcWidth(nZ);
					double metalSpacing = width / 2;
					boolean allClear = false;
					double initNX = nX, initNY = nY;
					String explanation = null;
					if (RoutingDebug.isActive()) explanation = "";
					for(;;)
					{
						SearchVertex prevPath = svCurrent;
						double checkX = (curX + nX) / 2, checkY = (curY + nY) / 2;
						double halfWid = metalSpacing + Math.abs(dx) / 2;
						double halfHei = metalSpacing + Math.abs(dy) / 2;
						while (prevPath != null && prevPath.last != null)
						{
							if (prevPath.zv != nZ || prevPath.last.zv != nZ) break;
							if (prevPath.xv == prevPath.last.xv && dx == 0)
							{
								checkY = (prevPath.last.yv + nY) / 2;
								halfHei = metalSpacing + Math.abs(prevPath.last.yv - nY) / 2;
								prevPath = prevPath.last;
							} else if (prevPath.yv == prevPath.last.yv && dy == 0)
							{
								checkX = (prevPath.last.xv + nX) / 2;
								halfWid = metalSpacing + Math.abs(prevPath.last.xv - nX) / 2;
								prevPath = prevPath.last;
							} else
								break;
						}
						SOGBound sb = getMetalBlockageAndNotch(nZ, halfWid, halfHei, checkX, checkY, prevPath);
						if (sb == null)
						{
							allClear = true;
							break;
						}
						if (RoutingDebug.isActive())
							explanation += ": Blocked on M" + (nZ+1) + " because proposed " +
								TextUtils.formatDouble(checkX-halfWid) +
								"<=X<=" + TextUtils.formatDouble(checkX+halfWid) +
								" and " + TextUtils.formatDouble(checkY-halfHei) +
								"<=Y<=" + TextUtils.formatDouble(checkY+halfHei) +
								" is less than " + TextUtils.formatDouble(metalSpacing) +
								" to " + TextUtils.formatDouble(sb.bound.getMinX()) +
								"<=X<=" + TextUtils.formatDouble(sb.bound.getMaxX()) +
								" and " + TextUtils.formatDouble(sb.bound.getMinY()) +
								"<=Y<=" + TextUtils.formatDouble(sb.bound.getMaxY());

						// see if it can be backed out slightly
						if (i == 0)
						{
							// moved left too far...try a bit to the right
							double newNX = nX + GRAINSIZE;
							if (inDestGrid(toRectGridded, newNX, curY)) newNX = nr.downToGrainAlways(newNX); else
								newNX = nr.getUpperXGrid(curZ, newNX);
							if (newNX >= curX || DBMath.areEquals(newNX, nX)) break;
							dx = newNX - curX;
							if (dx == 0) break;
						} else if (i == 1)
						{
							// moved right too far...try a bit to the left
							double newNX = nX - GRAINSIZE;
							if (inDestGrid(toRectGridded, newNX, curY)) newNX = nr.upToGrainAlways(newNX); else
								newNX = nr.getLowerXGrid(curZ, newNX);
							if (newNX <= curX || DBMath.areEquals(newNX, nX)) break;
							dx = newNX - curX;
							if (dx == 0) break;
						} else if (i == 2)
						{
							// moved down too far...try a bit up
							double newNY = nY + GRAINSIZE;
							if (inDestGrid(toRectGridded, curX, newNY)) newNY = nr.downToGrainAlways(newNY); else
								newNY = nr.getUpperYGrid(curZ, newNY);
							if (newNY >= curY || DBMath.areEquals(newNY, nY)) break;
							dy = newNY - curY;
							if (dy == 0) break;
						} else if (i == 3)
						{
							// moved up too far...try a bit down
							double newNY = nY - GRAINSIZE;
							if (inDestGrid(toRectGridded, curX, newNY)) newNY = nr.upToGrainAlways(newNY); else
								newNY = nr.getLowerYGrid(curZ, newNY);
							if (newNY <= curY || DBMath.areEquals(newNY, nY)) break;
							dy = newNY - curY;
							if (dy == 0) break;
						}

						nX = curX + dx;
						nY = curY + dy;
					}
					if (!allClear)
					{
						if (RoutingDebug.isActive())
						{
							double checkX = (curX + nX) / 2, checkY = (curY + nY) / 2;
							double halfWid = metalSpacing + Math.abs(dx) / 2;
							double halfHei = metalSpacing + Math.abs(dy) / 2;
							double surround = worstMetalSurround[nZ];
							SOGBound sb = nr.getMetalBlockage(nr.netID, nZ, halfWid, halfHei, new double[] {surround, surround}, checkX, checkY);
							if (sb != null) explanation += ": Blocked"; else
								explanation += ": Blocked, Notch";
							completeDebugString(i, explanation);
						}
						continue;
					}
					if (RoutingDebug.isActive())
					{
						if (initNX != nX || initNY != nY)
						{
							explanation += " so move only ";
							switch (i)
							{
								case 0: explanation += TextUtils.formatDouble(Math.abs(dx));  break;
								case 1: explanation += TextUtils.formatDouble(dx);            break;
								case 2: explanation += TextUtils.formatDouble(Math.abs(dy));  break;
								case 3: explanation += TextUtils.formatDouble(dy);            break;
							}
						}
						addDebugString(i, explanation);
					}
				} else
				{
					// switching layers
					int lowMetal = Math.min(curZ, nZ);
					int highMetal = Math.max(curZ, nZ);
					List<MetalVia> nps = metalVias[lowMetal].getVias();
					whichContact = -1;
					String[] failureReasons = null;
					if (RoutingDebug.isActive()) failureReasons = new String[nps.size()];
					for (int contactNo = 0; contactNo < nps.size(); contactNo++)
					{
						MetalVia mv = nps.get(contactNo);
						PrimitiveNode np = mv.via;
						Orientation orient = Orientation.fromJava(mv.orientation * 10, false, false);
						SizeOffset so = np.getProtoSizeOffset();
						double minWid = nr.minWidth;
						double minHei = nr.minWidth;
						double xOffset = so.getLowXOffset() + so.getHighXOffset();
						double yOffset = so.getLowYOffset() + so.getHighYOffset();
						double conWid = Math.max(np.getDefWidth(ep) - xOffset, minWid) + xOffset;
						double conHei = Math.max(np.getDefHeight(ep) - yOffset, minHei) + yOffset;
						boolean allowOverrides = false;
						if (mv.horMetal >= 0 && mv.verMetal >= 0)
						{
							if (sogp.isHorizontalEven())
							{
								if ((mv.horMetal+1)%2 == 0) allowOverrides = true;
							} else
							{
								if ((mv.verMetal+1)%2 != 0) allowOverrides = true;
							}
						}
						if (allowOverrides)
						{
							xOffset = mv.verMetalInset;
							yOffset = mv.horMetalInset;
							if (nr.getArcWidth(mv.verMetal) + xOffset > conWid) conWid = nr.getArcWidth(mv.verMetal) + xOffset;
							if (nr.getArcWidth(mv.horMetal) + yOffset > conHei) conHei = nr.getArcWidth(mv.horMetal) + yOffset;
						}
						NodeInst dummyNi = NodeInst.makeDummyInstance(np, ep, EPoint.fromLambda(nX, nY), conWid, conHei, orient);
						Poly[] conPolys = tech.getShapeOfNode(dummyNi);
						FixpTransform trans = null;
						if (orient != Orientation.IDENT) trans = dummyNi.rotateOut();

						// count the number of cuts and make an array for the data
						int cutCount = 0;
						for (int p = 0; p < conPolys.length; p++)
							if (conPolys[p].getLayer().getFunction().isContact()) cutCount++;
						Point2D[] curCuts = new Point2D[cutCount];
						cutCount = 0;
						String failedReason = null;
						for (int p = 0; p < conPolys.length; p++)
						{
							Poly conPoly = conPolys[p];
							if (trans != null) conPoly.transform(trans);
							Layer conLayer = conPoly.getLayer();
							Layer.Function lFun = conLayer.getFunction();
							if (lFun.isMetal())
							{
								Rectangle2D conRect = conPoly.getBounds2D();
								int metalNo = lFun.getLevel() - 1;
								double halfWid = conRect.getWidth() / 2;
								double halfHei = conRect.getHeight() / 2;
								SOGBound sb = getMetalBlockageAndNotch(metalNo, halfWid, halfHei, conRect.getCenterX(), conRect.getCenterY(), svCurrent);
								if (sb != null)
								{
									if (failureReasons == null) failedReason = ""; else
									{
										failedReason = "layer " + conLayer.getName();
										failedReason +=
											" at " + TextUtils.formatDouble(conRect.getMinX()) +
											"<=X<=" + TextUtils.formatDouble(conRect.getMaxX()) +
											" and " + TextUtils.formatDouble(conRect.getMinY()) +
											"<=Y<=" + TextUtils.formatDouble(conRect.getMaxY());
										failedReason +=
											" conflicts with " + TextUtils.formatDouble(sb.getBounds().getMinX()) +
											"<=X<=" + TextUtils.formatDouble(sb.getBounds().getMaxX()) +
											" and " + TextUtils.formatDouble(sb.getBounds().getMinY()) +
											"<=Y<=" + TextUtils.formatDouble(sb.getBounds().getMaxY());
									}
									break;
								}
							} else if (lFun.isContact())
							{
								// make sure vias don't get too close
								Rectangle2D conRect = conPoly.getBounds2D();
								double conCX = conRect.getCenterX();
								double conCY = conRect.getCenterY();
								double surround = viaSurround[lowMetal];
								if (nr.getViaBlockage(nr.netID, conLayer, surround, surround, conCX, conCY) != null)
								{
									if (failureReasons == null) failedReason = ""; else
										failedReason = "cut " + conLayer.getName() +
											" at " + TextUtils.formatDouble(conRect.getMinX()) +
											"<=X<=" + TextUtils.formatDouble(conRect.getMaxX()) +
											" and " + TextUtils.formatDouble(conRect.getMinY()) +
											"<=Y<=" + TextUtils.formatDouble(conRect.getMaxY());
									break;
								}
								curCuts[cutCount++] = new Point2D.Double(conCX, conCY);

								// look at all previous cuts in this path
								for (SearchVertex sv = svCurrent; sv != null; sv = sv.last)
								{
									SearchVertex lastSv = sv.last;
									if (lastSv == null) break;
									if (Math.min(sv.getZ(), lastSv.getZ()) == lowMetal &&
										Math.max(sv.getZ(), lastSv.getZ()) == highMetal)
									{
										// make sure the cut isn't too close
										Point2D[] svCuts;
										if (sv.getCutLayer() == lowMetal) svCuts = sv.getCuts(); else
											svCuts = lastSv.getCuts();
										if (svCuts != null)
										{
											for (Point2D cutPt : svCuts)
											{
												if (Math.abs(cutPt.getX() - conCX) >= surround ||
													Math.abs(cutPt.getY() - conCY) >= surround) continue;
												if (failureReasons == null) failedReason = ""; else
													failedReason = conLayer.getName() + " cut at (" + TextUtils.formatDouble(conCX) + "," +
														TextUtils.formatDouble(conCY) + ") because it is too close (" + surround + ") to previous cut at (" +
														TextUtils.formatDouble(sv.getX()) + "," + TextUtils.formatDouble(sv.getY()) + ")";
												break;
											}
										}
										if (failedReason != null) break;
									}
								}
								if (failedReason != null) break;
							}
						}
						if (failedReason != null)
						{
							if (RoutingDebug.isActive()) failureReasons[contactNo] = failedReason;
							continue;
						}
						whichContact = contactNo;
						cuts = curCuts;
						size = new Point2D.Double(conWid, conHei);
						break;
					}
					if (whichContact < 0)
					{
						if (RoutingDebug.isActive())
						{
							String further = ": Blocked because:";
							for(int contactNo = 0; contactNo < failureReasons.length; contactNo++)
							{
								MetalVia mv = nps.get(contactNo);
								further += "|In " + describe(mv.via);
								if (mv.orientation != 0) further += " (rotated " + mv.orientation + ")";
								further += " cannot place " + failureReasons[contactNo];
							}
							completeDebugString(i, further);
						}
						continue;
					}
				}

				// see if it found the destination
				boolean foundDest = DBMath.pointInRect(EPoint.fromLambda(nX, nY), toRect) && nZ == toZ;

				if (foundDestination != null)
					foundDest = true;

				// ignore if a large jump on the wrong metal layer
				if (foundDest)
				{
					if (Math.abs(dx) > 10 && (nZ % 2) == 0) continue;
					if (Math.abs(dy) > 10 && (nZ % 2) != 0) continue;
				}

				// we have a candidate next-point
				SearchVertex svNext = new SearchVertex(nX, nY, nZ, whichContact, cuts, size, Math.min(curZ, nZ), this);
				if (dz == 0 && (Math.abs(dx) >= 2 || Math.abs(dy) >= 2)) svNext.setAutoGen(i);
				svNext.last = svCurrent;

				if (foundDest)
				{
					if (RoutingDebug.isActive())
					{
						RoutingDebug.saveSVLink(svNext, i);
						completeDebugString(i, ": Found Destination!");
					}
					destinationSV = svNext;
					continue;
				}

				// determine any change to the global routing bucket
				if (globalRoutingDelta != 0)
					svNext.globalRoutingBucket = getNextBucket(svCurrent, nX, nY);

				// compute the cost
				int newCost = svCurrent.cost;
				String costExplanation = "";
				boolean penaltyOffGridX = false;
				if (nX < toRect.getMinX() || nX > toRect.getMaxX())
				{
					if (nr.gridLocationsX[nZ] == null)
					{
						penaltyOffGridX = nr.downToGrainAlways(nX) != nX;
					} else
					{
						penaltyOffGridX = !nr.isOnXGrid(nZ, nX);
					}
				}
				boolean penaltyOffGridY = false;
				if (nY < toRect.getMinY() || nY > toRect.getMaxY())
				{
					if (nr.gridLocationsY[nZ] == null)
					{
						penaltyOffGridY = nr.downToGrainAlways(nY) != nY;
					} else
					{
						penaltyOffGridY = !nr.isOnYGrid(nZ, nY);
					}
				}

				double distBefore = Math.sqrt((curX-toX)*(curX-toX) + (curY-toY)*(curY-toY));
				double distAfter = Math.sqrt((nX-toX)*(nX-toX) + (nY-toY)*(nY-toY));
				int c = (int)((distAfter - distBefore) / 5);
				newCost += c;
				if (RoutingDebug.isActive()) costExplanation = " [COST: Dist-to-target=" + c;

				if (dx != 0)
				{
					if (curX >= toRect.getMinX() && curX <= toRect.getMaxX())
					{
						c = COSTWRONGDIRECTION / 2;
						newCost += c;
						if (RoutingDebug.isActive()) costExplanation += " Zero-X-progress=" + c;
					} else if ((toX - curX) * dx < 0)
					{
						if (curY >= toRect.getMinY() && curY <= toRect.getMaxY())
						{
							c = 1;
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Backward-X-progress-at-dest-Y=" + c;
						} else
						{
							c = COSTWRONGDIRECTION;
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Backward-X-progress=" + c;
						}
					}
					if (sogp.isFavorHorVer())
					{
						if (((nZ % 2) == 0) == sogp.isHorizontalEven())
						{
							c = (int)Math.round(COSTALTERNATINGMETAL * Math.abs(dx));
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Not-alternating-metal=" + c;
						}
					}
				}
				if (dy != 0)
				{
					if (curY >= toRect.getMinY() && curY <= toRect.getMaxY())
					{
						c = COSTWRONGDIRECTION / 2;
						newCost += c;
						if (RoutingDebug.isActive()) costExplanation += " Zero-Y-progress=" + c;
					} else if ((toY - curY) * dy < 0)
					{
						if (curX >= toRect.getMinX() && curX <= toRect.getMaxX())
						{
							c = 1;
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Backward-Y-progress-at-dest-X=" + c;
						} else
						{
							c = COSTWRONGDIRECTION;
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Backward-Y-progress=" + c;
						}
					}
					if (sogp.isFavorHorVer())
					{
						if (((nZ % 2) != 0) == sogp.isHorizontalEven())
						{
							c = (int)Math.round(COSTALTERNATINGMETAL * Math.abs(dy));
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Not-alternating-metal=" + c;
						}
					}
				}
				if (dz != 0)
				{
					if (toZ == curZ)
					{
						c = COSTLAYERCHANGE;
						newCost += c;
						if (RoutingDebug.isActive()) costExplanation += " Layer-change=" + c;
					} else if ((toZ - curZ) * dz < 0)
					{
						c = COSTLAYERCHANGE + 1;

						// increase layer-change cost as distance from goal increases
//						int distFromGoal = Math.abs(toZ - (curZ + dz));
//						if (distFromGoal > 3) c *= (distFromGoal-3) * COSTLAYERCHANGE;

						newCost += c;
						if (RoutingDebug.isActive()) costExplanation += " Layer-change-wrong-direction=" + c;
					}
				} else
				{
					// not changing layers: compute penalty for unused tracks on either side of run
					double jumpSize1 = Math.abs(getJumpSize(svCurrent, nX, nY, nZ, dx, dy, null));
					double jumpSize2 = Math.abs(getJumpSize(svCurrent, curX, curY, curZ, -dx, -dy, null));
					if (jumpSize1 > GRAINSIZE && jumpSize2 > GRAINSIZE)
					{
						c = (int)((jumpSize1 * jumpSize2) / 10);
						if (c > 0)
						{
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Fragments-track=" + c;
						}
					}

					// not changing layers: penalize if turning in X or Y
					if (svCurrent.last != null)
					{
						boolean xTurn = svCurrent.getX() != svCurrent.last.getX();
						boolean yTurn = svCurrent.getY() != svCurrent.last.getY();
						if (xTurn != (dx != 0) || yTurn != (dy != 0))
						{
							c = COSTTURNING;
							newCost += c;
							if (RoutingDebug.isActive()) costExplanation += " Turning=" + c;
						}
					}
				}
				if (!favorArcs[nZ])
				{
					c = (int)((COSTLAYERCHANGE * COSTUNFAVORED) * Math.abs(dz) + COSTUNFAVORED * Math.abs(dx + dy));
					newCost += c;
					if (RoutingDebug.isActive()) costExplanation += " Layer-unfavored=" + c;
				}
				if (penaltyOffGridX)
				{
					c = COSTOFFGRID;
					newCost += c;
					if (RoutingDebug.isActive()) costExplanation += " Off-X-grid=" + c;
				}
				if (penaltyOffGridY)
				{
					c = COSTOFFGRID;
					newCost += c;
					if (RoutingDebug.isActive()) costExplanation += " Off-Y-grid=" + c;
				}
				svNext.cost = newCost;

				// replace pending SearchVertex with this if cost is better
				if (alreadyThere != null)
				{
					if (alreadyThere.getCost() < svNext.getCost())
					{
						if (RoutingDebug.isActive()) completeDebugString(i, ": Already Planned at lower cost (" + alreadyThere.getCost() + ")");
						continue;
					}
					active.remove(alreadyThere);
				}

				// add this vertex into the data structures
				setVertex(nX, nY, nZ, svNext);
				active.add(svNext);
				if (RoutingDebug.isActive())
				{
					completeDebugString(i, ": To (" + TextUtils.formatDouble(svNext.getX()) + "," + TextUtils.formatDouble(svNext.getY()) +
						",M" + (svNext.getZ() + 1) + ")" + costExplanation + "]");
				}
				if (RoutingDebug.isActive())
					RoutingDebug.saveSVLink(svNext, i);
			}
			if (RoutingDebug.isActive())
				RoutingDebug.saveSVDetails(svCurrent, debugString);
			return destinationSV;
		}

		private int getNextBucket(SearchVertex svCurrent, double nX, double nY)
		{
			int start = orderedBase[svCurrent.globalRoutingBucket];
			for(int bucket = start; ; bucket += globalRoutingDelta)
			{
				if (bucket < 0 || bucket >= nr.buckets.length) break;
				Rectangle2D limit = nr.buckets[bucket];
				if (DBMath.isGreaterThanOrEqualTo(nX, limit.getMinX()) && DBMath.isLessThanOrEqualTo(nX, limit.getMaxX()) &&
					DBMath.isGreaterThanOrEqualTo(nY, limit.getMinY()) && DBMath.isLessThanOrEqualTo(nY, limit.getMaxY()))
						return bucket;
			}

			// not found where it should be, look in the other direction
			for(int bucket = start-globalRoutingDelta; ; bucket -= globalRoutingDelta)
			{
				if (bucket < 0 || bucket >= nr.buckets.length) break;
				Rectangle2D limit = nr.buckets[bucket];
				if (DBMath.isGreaterThanOrEqualTo(nX, limit.getMinX()) && DBMath.isLessThanOrEqualTo(nX, limit.getMaxX()) &&
					DBMath.isGreaterThanOrEqualTo(nY, limit.getMinY()) && DBMath.isLessThanOrEqualTo(nY, limit.getMaxY()))
						return bucket;
			}

			// not found at all: an error
			error("ERROR: Could not find next bucket going from ("+TextUtils.formatDouble(svCurrent.xv)+","+
				TextUtils.formatDouble(svCurrent.yv)+") to ("+TextUtils.formatDouble(nX)+","+TextUtils.formatDouble(nY)+
				") starting at bucket "+orderedBase[svCurrent.globalRoutingBucket]+
				" (really "+svCurrent.globalRoutingBucket+") and going "+globalRoutingDelta);
			return svCurrent.globalRoutingBucket;
		}

		/**
		 * Method to create the geometry for a winning wavefront. Places nodes and arcs to make
		 * the route, and also updates the R-Tree data structure.
		 */
		public void createRoute()
		{
			String routeName = nr.routeName;
			if (routeName.endsWith("...")) routeName = routeName.substring(0, routeName.length()-3);
			int parenPos = routeName.lastIndexOf('(');
			if (parenPos > 0) routeName = routeName.substring(0, parenPos);

			RouteNode fromRN = new RouteNode(from);
			RouteNode toRN = new RouteNode(to);
			RouteNode lastRN = toRN;
			PolyBase toPoly = to.getPoly();
			double minWidth = nr.minWidth;
			RouteResolution resolution = nr.batch.resolution;
			if (!DBMath.pointInRect(EPoint.fromLambda(toX, toY), toRect))
			{
				// end of route is off-grid: adjust it
				if (vertices.size() >= 2)
				{
					SearchVertex v1 = vertices.get(0);
					SearchVertex v2 = vertices.get(1);
					ArcProto type = metalArcs[toZ];
					Layer layer = metalLayers[toZ];
					double width = nr.getArcWidth(toZ);
					PrimitiveNode np = metalArcs[toZ].findPinProto();
					if (v1.getX() == v2.getX())
					{
						// first line is vertical: run a horizontal bit
						RouteNode rn = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(v1.getX(), toPoly.getCenterY()),
							np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
						resolution.addNode(rn);
						RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, rn, toRN, nr);
						routeName = null;
						resolution.addArc(ra);
						lastRN = rn;
					} else if (v1.getY() == v2.getY())
					{
						// first line is horizontal: run a vertical bit
						RouteNode rn = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(toPoly.getCenterX(), v1.getY()),
							np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
						resolution.addNode(rn);
						RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, rn, toRN, nr);
						routeName = null;
						resolution.addArc(ra);
						lastRN = rn;
					}
				}
			}
			for (int i = 0; i < vertices.size(); i++)
			{
				SearchVertex sv = vertices.get(i);
				boolean madeContacts = false;
				while (i < vertices.size() - 1)
				{
					SearchVertex svNext = vertices.get(i + 1);
					if (sv.getX() != svNext.getX() || sv.getY() != svNext.getY() || sv.getZ() == svNext.getZ()) break;
					List<MetalVia> nps = metalVias[Math.min(sv.getZ(), svNext.getZ())].getVias();
					int whichContact = sv.getContactNo();
					MetalVia mv = nps.get(whichContact);
					PrimitiveNode np = mv.via;
					Orientation orient = Orientation.fromJava(mv.orientation * 10, false, false);
					SizeOffset so = np.getProtoSizeOffset();
					double xOffset = so.getLowXOffset() + so.getHighXOffset();
					double yOffset = so.getLowYOffset() + so.getHighYOffset();
					double wid = Math.max(np.getDefWidth(ep) - xOffset, minWidth) + xOffset;
					double hei = Math.max(np.getDefHeight(ep) - yOffset, minWidth) + yOffset;
					Point2D size = sv.getSize();
					wid = size.getX();   hei = size.getY();
					ArcProto type = metalArcs[sv.getZ()];
					Layer layer = metalLayers[sv.getZ()];
					double width = nr.getArcWidth(sv.getZ());
					RouteNode rn = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(sv.getX(), sv.getY()), wid, hei, orient, null, nr);
					resolution.addNode(rn);
					RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, lastRN, rn, nr);
					routeName = null;
					resolution.addArc(ra);
					lastRN = rn;
					madeContacts = true;
					sv = svNext;
					i++;
				}
				if (madeContacts && i != vertices.size() - 1) continue;

				PrimitiveNode np = metalArcs[sv.getZ()].findPinProto();
				RouteNode piRN;
				if (i == vertices.size() - 1)
				{
					piRN = fromRN;
					if (!DBMath.pointInRect(EPoint.fromLambda(sv.getX(), sv.getY()), fromRect))
					{
						// end of route is off-grid: adjust it
						if (vertices.size() >= 2)
						{
							SearchVertex v1 = vertices.get(vertices.size() - 2);
							SearchVertex v2 = vertices.get(vertices.size() - 1);
							ArcProto type = metalArcs[fromZ];
							Layer layer = metalLayers[fromZ];
							double width = nr.getArcWidth(fromZ);
							if (v1.getX() == v2.getX())
							{
								// last line is vertical: run a horizontal bit
								PrimitiveNode pNp = metalArcs[fromZ].findPinProto();
								piRN = new RouteNode(pNp, SeaOfGatesEngine.this, EPoint.fromLambda(v1.getX(), fromY),
									np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
								resolution.addNode(piRN);
								RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, piRN, fromRN, nr);
								routeName = null;
								resolution.addArc(ra);
							} else if (v1.getY() == v2.getY())
							{
								// last line is horizontal: run a vertical bit
								PrimitiveNode pNp = metalArcs[fromZ].findPinProto();
								piRN = new RouteNode(pNp, SeaOfGatesEngine.this, EPoint.fromLambda(fromX, v1.getY()),
									np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
								resolution.addNode(piRN);
								RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, piRN, fromRN, nr);
								routeName = null;
								resolution.addArc(ra);
							}
						}
					}
				} else
				{
					PortInst pi = null;
					if (nr.spineTapMap != null) pi = nr.spineTapMap.get(sv);
					piRN = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(sv.getX(), sv.getY()),
						np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, pi, nr);
					resolution.addNode(piRN);
				}
				if (lastRN != null)
				{
					ArcProto type = metalArcs[sv.getZ()];
					Layer layer = metalLayers[sv.getZ()];
					double width = nr.getArcWidth(sv.getZ());
					if (ANYPOINTONDESTINATION && i == 0)
					{
						if (!DBMath.rectsIntersect(lastRN.rect, piRN.rect))
						{
							// is the layer present for extending the arc?
							double fX = lastRN.getLoc().getX(), fY = lastRN.getLoc().getY();
							double tX = piRN.getLoc().getX(), tY = piRN.getLoc().getY();
							double lX = Math.min(fX, tX) - width/2;
							double hX = Math.max(fX, tX) + width/2;
							double lY = Math.min(fY, tY) - width/2;
							double hY = Math.max(fY, tY) + width/2;
							Rectangle2D searchArea = new Rectangle2D.Double(lX, lY, hX-lX, hY-lY);
							BlockageTree bTree = rTrees.getMetalTree(layer);
							boolean covered = false;
							bTree.lock();
							try {
								if (!bTree.isEmpty())
								{
									for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext();)
									{
										SOGBound sBound = sea.next();
										Rectangle2D bound = sBound.getBounds();
										if (bound.getMinX() <= lX && bound.getMaxX() >= hX && bound.getMinY() <= lY && bound.getMaxY() >= hY)
										{
											covered = true;
											break;
										}
									}
								}
							} finally {
								bTree.unlock();
							}
							if (!covered)
							{
								boolean geomOK = false;
								if (fX == tX || fY == tY)
								{
									// try running a single arc
									SOGBound errSV = getMetalBlockageAndNotch(sv.getZ(), (hX-lX)/2, (hY-lY)/2, (hX+lX)/2, (hY+lY)/2, null);
									if (errSV == null) geomOK = true;
								} else
								{
									// try running two arcs
									double bend1X = fX, bend1Y = tY;
									double bend2X = tX, bend2Y = fY;

									double bend1aLX = lX, bend1aHX = lX + width;
									double bend1aLY = lY, bend1aHY = hY;

									double bend1bLX = lX, bend1bHX = hX;
									double bend1bLY = hY - width, bend1bHY = hY;

									double bend2aLX = lX, bend2aHX = hX;
									double bend2aLY = lY, bend2aHY = lY + width;

									double bend2bLX = hX - width, bend2bHX = hX;
									double bend2bLY = lY, bend2bHY = hY;

									SOGBound errSVa = getMetalBlockageAndNotch(sv.getZ(), (bend1aHX-bend1aLX)/2, (bend1aHY-bend1aLY)/2, (bend1aHX+bend1aLX)/2, (bend1aHY+bend1aLY)/2, null);
									SOGBound errSVb = getMetalBlockageAndNotch(sv.getZ(), (bend1bHX-bend1bLX)/2, (bend1bHY-bend1bLY)/2, (bend1bHX+bend1bLX)/2, (bend1bHY+bend1bLY)/2, null);
									if (errSVa == null && errSVb == null)
									{
										RouteNode bend1RN = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(bend1X, bend1Y),
											np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
										resolution.addNode(bend1RN);
										RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, piRN, bend1RN, nr);
										routeName = null;
										resolution.addArc(ra);
										piRN = bend1RN;
										geomOK = true;
										System.out.println("AVOIDED UNIVERSAL ARC BECAUSE BEND 1 WORKS");
									} else
									{
										errSVa = getMetalBlockageAndNotch(sv.getZ(), (bend2aHX-bend2aLX)/2, (bend2aHY-bend2aLY)/2, (bend2aHX+bend2aLX)/2, (bend2aHY+bend2aLY)/2, null);
										errSVb = getMetalBlockageAndNotch(sv.getZ(), (bend2bHX-bend2bLX)/2, (bend2bHY-bend2bLY)/2, (bend2bHX+bend2bLX)/2, (bend2bHY+bend2bLY)/2, null);
										if (errSVa == null && errSVb == null)
										{
											RouteNode bend2RN = new RouteNode(np, SeaOfGatesEngine.this, EPoint.fromLambda(bend2X, bend2Y),
												np.getDefWidth(ep), np.getDefHeight(ep), Orientation.IDENT, null, nr);
											resolution.addNode(bend2RN);
											RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, piRN, bend2RN, nr);
											routeName = null;
											resolution.addArc(ra);
											piRN = bend2RN;
											geomOK = true;
											System.out.println("AVOIDED UNIVERSAL ARC BECAUSE BEND 2 WORKS");
										}
									}
								}
								if (!geomOK)
								{
System.out.println("WARNING: Placing Universal are on route from port "+from.getPortProto().getName()+" of node "+describe(from.getNodeInst())+
	" AT ("+fromX+","+fromY+",M"+(fromZ+1)+") to port "+to.getPortProto().getName()+" of node "+describe(to.getNodeInst())+
	" AT ("+toX+","+toY+",M"+(toZ+1)+")");
//for(SearchVertex v : vertices)
//	System.out.println("   SEARCHVERTEX ("+v.xv+","+v.yv+",M"+(v.getZ()+1)+")");

									type = Generic.tech().universal_arc;
									layer = null;
									width = 0;
								} else System.out.println("AVOIDED UNIVERSAL ARC BECAUSE EXISTING LAYER IS DRC CLEAN");
							} else System.out.println("AVOIDED UNIVERSAL ARC BECAUSE EXISTING LAYER SURROUNDS OK");
						}
					}
					RouteArc ra = new RouteArc(type, routeName, SeaOfGatesEngine.this, layer, width, lastRN, piRN, nr);
					routeName = null;
					resolution.addArc(ra);
				}
				lastRN = piRN;
			}

			if (nr.endBlockages != null)
			{
				List<Layer> allLayers = new ArrayList<Layer>();
				for(Layer lay : nr.endBlockages.keySet()) allLayers.add(lay);
				for(Layer lay : allLayers)
				{
					BlockageTree bTree = rTrees.getMetalTree(lay);
					bTree.lock();
					try {
						List<SOGBound> endBlocks = nr.endBlockages.get(lay);
						for(SOGBound endBlock : endBlocks)
						{
							RTNode<SOGBound> origRoot = bTree.getRoot();
							RTNode<SOGBound> newRoot = RTNode.unLinkGeom(null, origRoot, endBlock, false);
							if (newRoot != origRoot)
								bTree.setRoot(newRoot);
						}
						nr.endBlockages.remove(lay);
					} finally {
						bTree.unlock();
					}
				}
			}
		}

		private double getJumpSize(SearchVertex sv, double curX, double curY, int curZ, double dx, double dy, MutableBoolean foundDestination)
		{
			Rectangle2D jumpBound = nr.jumpBound;
			double width = nr.getArcWidth(curZ);
			double[] fromSurround = nr.getSpacingRule(curZ, width, 50);
			double metalSpacingX = width / 2 + fromSurround[0];
			double metalSpacingY = width / 2 + fromSurround[1];
			double lX = curX - metalSpacingX, hX = curX + metalSpacingX;
			double lY = curY - metalSpacingY, hY = curY + metalSpacingY;
			if (dx > 0) hX = jumpBound.getMaxX() + metalSpacingX; else
				if (dx < 0) lX = jumpBound.getMinX() - metalSpacingX; else
					if (dy > 0) hY = jumpBound.getMaxY() + metalSpacingY; else
						if (dy < 0) lY = jumpBound.getMinY() - metalSpacingY;

			BlockageTree bTree = rTrees.getMetalTree(metalLayers[curZ]);
			bTree.lock();
			try {
				if (!bTree.isEmpty())
				{
					// see if there is anything in that area
					boolean hitDestination = false;
					Rectangle2D searchArea = new Rectangle2D.Double(lX, lY, hX - lX, hY - lY);
					for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext();)
					{
						SOGBound sBound = sea.next();
						Rectangle2D bound = sBound.getBounds();
						if (sBound.isSameBasicNet(nr.netID))
						{
							if (foundDestination != null && sBound.hasEndBit(toBit))
							{
								if (dx > 0 && bound.getCenterX() + metalSpacingX < hX) { hX = bound.getCenterX() + metalSpacingX;  hitDestination = true; }
								if (dx < 0 && bound.getCenterX() - metalSpacingX > lX) { lX = bound.getCenterX() - metalSpacingX;  hitDestination = true; }
								if (dy > 0 && bound.getCenterY() + metalSpacingY < hY) { hY = bound.getCenterY() + metalSpacingY;  hitDestination = true; }
								if (dy < 0 && bound.getCenterY() - metalSpacingY > lY) { lY = bound.getCenterY() - metalSpacingY;  hitDestination = true; }
							}
							continue;
						}
						if (bound.getMinX() >= hX || bound.getMaxX() <= lX || bound.getMinY() >= hY || bound.getMaxY() <= lY)
							continue;
						if (dx > 0 && bound.getMinX() < hX) { hX = bound.getMinX();  hitDestination = false; }
						if (dx < 0 && bound.getMaxX() > lX) { lX = bound.getMaxX();  hitDestination = false; }
						if (dy > 0 && bound.getMinY() < hY) { hY = bound.getMinY();  hitDestination = false; }
						if (dy < 0 && bound.getMaxY() > lY) { lY = bound.getMaxY();  hitDestination = false; }
					}
					if (foundDestination != null) foundDestination.setValue(hitDestination);
				}
			} finally {
				bTree.unlock();
			}
			if (dx > 0)
			{
				dx = nr.downToGrain(hX - metalSpacingX) - curX;
				if (curX + dx > toX && curY >= toRect.getMinY() && curY <= toRect.getMaxY() && curZ == toZ) dx = toX - curX; else
				{
					if (!inDestGrid(toRectGridded, curX+dx, curY))
						dx = nr.getLowerXGrid(curZ, curX+dx) - curX;
					if (globalRoutingDelta != 0)
					{
						Rectangle2D limit = orderedBuckets[sv.globalRoutingBucket];
						if (curX + dx > limit.getMaxX())
						{
							Rectangle2D originalBucket = nr.buckets[sv.globalRoutingBucket];
							dx = limit.getMaxX() - curX;

							// if moved into a new bucket, center it
							if (curX >= originalBucket.getMinX() && curX >= originalBucket.getMaxX() && curX + dx > originalBucket.getMaxX())
								dx -= originalBucket.getWidth()/2;
							dx = nr.getClosestXGrid(curZ, curX+dx) - curX;
						}
					}
					if (curX + dx > toRect.getMaxX() || curX + dx < toRect.getMinX()) dx = nr.downToGrainAlways(curX + dx) - curX;
				}
				return dx;
			}
			if (dx < 0)
			{
				dx = nr.upToGrain(lX + metalSpacingX) - curX;
				if (curX + dx < toX && curY >= toRect.getMinY() && curY <= toRect.getMaxY() && curZ == toZ) dx = toX - curX; else
				{
					if (!inDestGrid(toRectGridded, curX+dx, curY))
						dx = nr.getUpperXGrid(curZ, curX+dx) - curX;
					if (globalRoutingDelta != 0)
					{
						Rectangle2D limit = orderedBuckets[sv.globalRoutingBucket];
						if (curX + dx < limit.getMinX())
						{
							Rectangle2D originalBucket = nr.buckets[sv.globalRoutingBucket];
							dx = limit.getMinX() - curX;

							// if moved into a new bucket, center it
							if (curX >= originalBucket.getMinX() && curX >= originalBucket.getMaxX() && curX + dx < originalBucket.getMinX())
								dx += originalBucket.getWidth()/2;
							dx = nr.getClosestXGrid(curZ, curX+dx) - curX;
						}
					}
					if (curX + dx > toRect.getMaxX() || curX + dx < toRect.getMinX()) dx = nr.upToGrainAlways(curX + dx) - curX;
				}
				return dx;
			}
			if (dy > 0)
			{
				dy = nr.downToGrain(hY - metalSpacingY) - curY;
				if (curX >= toRect.getMinX() && curX <= toRect.getMaxX() && curY + dy > toY && curZ == toZ) dy = toY - curY; else
				{
					if (!inDestGrid(toRectGridded, curX, curY+dy))
						dy = nr.getLowerYGrid(curZ, curY+dy) - curY;
					if (globalRoutingDelta != 0)
					{
						Rectangle2D limit = orderedBuckets[sv.globalRoutingBucket];
						if (curY + dy > limit.getMaxY())
						{
							Rectangle2D originalBucket = nr.buckets[sv.globalRoutingBucket];
							dy = limit.getMaxY() - curY;

							// if moved into a new bucket, center it
							if (curY >= originalBucket.getMinY() && curY >= originalBucket.getMaxY() && curY + dy > originalBucket.getMaxY())
								dy -= originalBucket.getHeight()/2;
							dy = nr.getClosestYGrid(curZ, curY+dy) - curY;
						}
					}
					if (curY + dy > toRect.getMaxY() || curY + dy < toRect.getMinY()) dy = nr.downToGrainAlways(curY + dy) - curY;
				}
				return dy;
			}
			if (dy < 0)
			{
				dy = nr.upToGrain(lY + metalSpacingY) - curY;
				if (curX >= toRect.getMinX() && curX <= toRect.getMaxX() && curY + dy < toY && curZ == toZ) dy = toY - curY; else
				{
					if (!inDestGrid(toRectGridded, curX, curY+dy))
						dy = nr.getUpperYGrid(curZ, curY+dy) - curY;
					if (globalRoutingDelta != 0)
					{
						Rectangle2D limit = orderedBuckets[sv.globalRoutingBucket];
						if (curY + dy < limit.getMinY())
						{
							Rectangle2D originalBucket = nr.buckets[sv.globalRoutingBucket];
							dy = limit.getMinY() - curY;

							// if moved into a new bucket, center it
							if (curY >= originalBucket.getMinY() && curY >= originalBucket.getMaxY() && curY + dy < originalBucket.getMinY())
								dy += originalBucket.getHeight()/2;
							dy = nr.getClosestYGrid(curZ, curY+dy) - curY;
						}
					}
					if (curY + dy > toRect.getMaxY() || curY + dy < toRect.getMinY()) dy = nr.upToGrainAlways(curY + dy) - curY;
				}
				return dy;
			}
			return 0;
		}

		/**
		 * Method to see if a proposed piece of metal has DRC errors.
		 * @param metNo the level of the metal.
		 * @param halfWidth half of the width of the metal.
		 * @param halfHeight half of the height of the metal.
		 * @param x the X coordinate at the center of the metal.
		 * @param y the Y coordinate at the center of the metal.
		 * @param svCurrent the list of SearchVertex's for finding notch errors in the current path.
		 * @return a blocking SOGBound object that is in the area. Returns null if the area is clear.
		 */
		private SOGBound getMetalBlockageAndNotch(int metNo, double halfWidth, double halfHeight,
			double x, double y, SearchVertex svCurrent)
		{
			// get the R-Tree data for the metal layer
			Layer layer = metalLayers[metNo];
			BlockageTree bTree = rTrees.getMetalTree(layer);
			bTree.lock();
			try {
				if (bTree.isEmpty()) return null;

				// determine the size and width/length of this piece of metal
				double minWidth = nr.minWidth;
				double metLX = x - halfWidth, metHX = x + halfWidth;
				double metLY = y - halfHeight, metHY = y + halfHeight;
				Rectangle2D metBound = new Rectangle2D.Double(metLX, metLY, metHX - metLX, metHY - metLY);
				double metWid = Math.min(halfWidth, halfHeight) * 2;
				double metLen = Math.max(halfWidth, halfHeight) * 2;

				// determine the area to search about the metal
				double surround = worstMetalSurround[metNo];
				double lX = metLX - surround, hX = metHX + surround;
				double lY = metLY - surround, hY = metHY + surround;
				Rectangle2D searchArea = new Rectangle2D.Double(lX, lY, hX - lX, hY - lY);

				// prepare for notch detection
				List<Rectangle2D> recsOnPath = new ArrayList<Rectangle2D>();

				// make a list of rectangles on the path
				if (svCurrent != null)
				{
					getOptimizedList(svCurrent, optimizedList);
					for (int ind = 1; ind < optimizedList.size(); ind++)
					{
						SearchVertex sv = optimizedList.get(ind);
						SearchVertex lastSv = optimizedList.get(ind - 1);
						if (sv.getZ() != metNo && lastSv.getZ() != metNo) continue;
						if (sv.getZ() != lastSv.getZ())
						{
							// changed layers: compute via rectangles
							List<MetalVia> nps = metalVias[Math.min(sv.getZ(), lastSv.getZ())].getVias();
							int whichContact = lastSv.getContactNo();
							MetalVia mv = nps.get(whichContact);
							PrimitiveNode np = mv.via;
							Orientation orient = Orientation.fromJava(mv.orientation * 10, false, false);
							SizeOffset so = np.getProtoSizeOffset();
							double xOffset = so.getLowXOffset() + so.getHighXOffset();
							double yOffset = so.getLowYOffset() + so.getHighYOffset();
							double wid = Math.max(np.getDefWidth(ep) - xOffset, minWidth) + xOffset;
							double hei = Math.max(np.getDefHeight(ep) - yOffset, minWidth) + yOffset;
							NodeInst ni = NodeInst.makeDummyInstance(np, ep, EPoint.fromLambda(sv.getX(), sv.getY()), wid, hei, orient);
							FixpTransform trans = null;
							if (orient != Orientation.IDENT) trans = ni.rotateOut();
							Poly[] polys = np.getTechnology().getShapeOfNode(ni);
							for (int i = 0; i < polys.length; i++)
							{
								Poly poly = polys[i];
								if (poly.getLayer() != layer) continue;
								if (trans != null) poly.transform(trans);
								Rectangle2D bound = poly.getBounds2D();
								if (bound.getMaxX() <= lX || bound.getMinX() >= hX || bound.getMaxY() <= lY || bound.getMinY() >= hY)
									continue;
								recsOnPath.add(bound);
							}
							continue;
						}

						// stayed on one layer: compute arc rectangle
						double width = nr.getArcWidth(metNo);
						Point2D head = new Point2D.Double(sv.getX(), sv.getY());
						Point2D tail = new Point2D.Double(lastSv.getX(), lastSv.getY());
						int ang = 0;
						if (head.getX() != tail.getX() || head.getY() != tail.getY())
							ang = GenMath.figureAngle(tail, head);
						Poly poly = Poly.makeEndPointPoly(head.distance(tail), width, ang, head, width / 2, tail,
							width / 2, Poly.Type.FILLED);
						Rectangle2D bound = poly.getBounds2D();
						if (bound.getMaxX() <= lX || bound.getMinX() >= hX || bound.getMaxY() <= lY || bound.getMinY() >= hY)
							continue;
						recsOnPath.add(bound);
					}
				}

				for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext(); )
				{
					SOGBound sBound = sea.next();
					Rectangle2D bound = sBound.getBounds();

					// eliminate if out of worst surround
					if (bound.getMaxX() <= lX || bound.getMinX() >= hX || bound.getMaxY() <= lY || bound.getMinY() >= hY)
						continue;

					// see if it is within design-rule distance
					double drWid = 0, drLen = 0;
					// TODO: determine proper width/length
//					if (metBound.getMinX() <= bound.getMaxX() && metBound.getMaxX() >= bound.getMinX())
//					{
//						// geometry is stacked vertically
//						drWid = Math.max(bound.getHeight(), metWid);
//						drLen = Math.max(bound.getWidth(), metLen);
//					} else if (metBound.getMinY() <= bound.getMaxY() && metBound.getMaxY() >= bound.getMinY())
//					{
//						// geometry is stacked horizontally
//						drWid = Math.max(bound.getWidth(), metWid);
//						drLen = Math.max(bound.getHeight(), metLen);
//					} else
					{
//						drWid = Math.max(Math.min(bound.getWidth(), bound.getHeight()), metWid);
//						drLen = Math.max(Math.max(bound.getWidth(), bound.getHeight()), metLen);
						drWid = Math.max(Math.min(bound.getWidth(), bound.getHeight()), Math.min(metWid, metLen));
						drLen = Math.max(Math.max(bound.getWidth(), bound.getHeight()), Math.max(metWid, metLen));
					}
					double[] spacing = nr.getSpacingRule(metNo, drWid, drLen);
					double lXAllow = metLX - spacing[0], hXAllow = metHX + spacing[0];
					double lYAllow = metLY - spacing[1], hYAllow = metHY + spacing[1];
					if (DBMath.isLessThanOrEqualTo(bound.getMaxX(), lXAllow) ||
						DBMath.isGreaterThanOrEqualTo(bound.getMinX(), hXAllow) ||
						DBMath.isLessThanOrEqualTo(bound.getMaxY(), lYAllow) ||
						DBMath.isGreaterThanOrEqualTo(bound.getMinY(), hYAllow)) continue;

					// too close for DRC: allow if on the same net
					if (sBound.isSameBasicNet(nr.netID))
					{
						// on same net: make sure there is no notch error
						boolean notBlockage = false;
						if (!sBound.isPseudoBlockage()) notBlockage = true;
						if (notBlockage)
						{
							boolean notch = foundANotch(bTree, metBound, bound, nr.netID, recsOnPath, spacing);
							if (notch) return sBound;
						}
						continue;
					}

					// if this is a polygon, do closer examination
					if (sBound instanceof SOGPoly)
					{
						PolyBase poly = ((SOGPoly) sBound).getPoly();
						Rectangle2D drcArea = new Rectangle2D.Double(lXAllow, lYAllow, hXAllow - lXAllow, hYAllow - lYAllow);
						if (!poly.contains(drcArea)) continue;
					}

					// DRC error found: return the offending geometry
					return sBound;
				}

				// consider notch errors in the existing path
				double[] spacing = nr.getSpacingRule(metNo, Math.min(metWid, metLen), Math.max(metWid, metLen));
				for(Rectangle2D bound : recsOnPath)
				{
					if (foundANotch(bTree, metBound, bound, nr.netID, recsOnPath, spacing))
						return new SOGBound(ERectangle.fromLambda(bound), nr.netID);
				}
				return null;
			} finally {
				bTree.unlock();
			}
		}

		/**
		 * Method to tell whether there is a notch between two pieces of metal.
		 * @param bTree the BlocakgeTree with the metal information.
		 * @param metBound one piece of metal.
		 * @param bound another piece of metal.
		 * @return true if there is a notch error between the pieces of metal.
		 */
		private boolean foundANotch(BlockageTree bTree, Rectangle2D metBound, Rectangle2D bound, MutableInteger netID,
			List<Rectangle2D> recsOnPath, double[] dist)
		{
			// see if they overlap in X or Y
			boolean hOverlap = metBound.getMinX() <= bound.getMaxX() && metBound.getMaxX() >= bound.getMinX();
			boolean vOverlap = metBound.getMinY() <= bound.getMaxY() && metBound.getMaxY() >= bound.getMinY();

			// if they overlap in both, they touch and it is not a notch
			if (hOverlap && vOverlap) return false;

			// if they overlap horizontally then they line-up vertically
			if (hOverlap)
			{
				double ptY;
				if (metBound.getCenterY() > bound.getCenterY())
				{
					if (metBound.getMinY() - bound.getMaxY() > dist[1]) return false;
					ptY = (metBound.getMinY() + bound.getMaxY()) / 2;
				} else
				{
					if (bound.getMinY() - metBound.getMaxY() > dist[1]) return false;
					ptY = (metBound.getMaxY() + bound.getMinY()) / 2;
				}
				double pt1X = Math.max(metBound.getMinX(), bound.getMinX());
				double pt2X = Math.min(metBound.getMaxX(), bound.getMaxX());
				double pt3X = (pt1X + pt2X) / 2;
				if (!pointInRTree(bTree, pt1X, ptY, netID, recsOnPath)) return true;
				if (!pointInRTree(bTree, pt2X, ptY, netID, recsOnPath)) return true;
				if (!pointInRTree(bTree, pt3X, ptY, netID, recsOnPath)) return true;
				return false;
			}

			// if they overlap vertically then they line-up horizontally
			if (vOverlap)
			{
				double ptX;
				if (metBound.getCenterX() > bound.getCenterX())
				{
					if (metBound.getMinX() - bound.getMaxX() > dist[0]) return false;
					ptX = (metBound.getMinX() + bound.getMaxX()) / 2;
				} else
				{
					if (bound.getMinX() - metBound.getMaxX() > dist[0]) return false;
					ptX = (metBound.getMaxX() + bound.getMinX()) / 2;
				}
				double pt1Y = Math.max(metBound.getMinY(), bound.getMinY());
				double pt2Y = Math.min(metBound.getMaxY(), bound.getMaxY());
				double pt3Y = (pt1Y + pt2Y) / 2;
				if (!pointInRTree(bTree, ptX, pt1Y, netID, recsOnPath)) return true;
				if (!pointInRTree(bTree, ptX, pt2Y, netID, recsOnPath)) return true;
				if (!pointInRTree(bTree, ptX, pt3Y, netID, recsOnPath)) return true;
				return false;
			}

			// they are diagonal, ensure that one of the "L"s is filled
			if (metBound.getMinX() > bound.getMaxX() && metBound.getMinY() > bound.getMaxY())
			{
				// metal to upper-right of test area
				double pt1X = metBound.getMinX();
				double pt1Y = bound.getMaxY();
				double pt2X = bound.getMaxX();
				double pt2Y = metBound.getMinY();
				if (Math.sqrt((pt1X - pt2X) * (pt1X - pt2X) + (pt1Y - pt2Y) * (pt1Y - pt2Y)) > Math.max(dist[0], dist[1])) return false;
				if (pointInRTree(bTree, pt1X, pt1Y, netID, recsOnPath)) return false;
				if (pointInRTree(bTree, pt2X, pt2Y, netID, recsOnPath)) return false;
				return true;
			}
			if (metBound.getMaxX() < bound.getMinX() && metBound.getMinY() > bound.getMaxY())
			{
				// metal to upper-left of test area
				double pt1X = metBound.getMaxX();
				double pt1Y = bound.getMaxY();
				double pt2X = bound.getMinX();
				double pt2Y = metBound.getMinY();
				if (Math.sqrt((pt1X - pt2X) * (pt1X - pt2X) + (pt1Y - pt2Y) * (pt1Y - pt2Y)) > Math.max(dist[0], dist[1])) return false;
				if (pointInRTree(bTree, pt1X, pt1Y, netID, recsOnPath)) return false;
				if (pointInRTree(bTree, pt2X, pt2Y, netID, recsOnPath)) return false;
				return true;
			}
			if (metBound.getMaxX() < bound.getMinX() && metBound.getMaxY() < bound.getMinY())
			{
				// metal to lower-left of test area
				double pt1X = metBound.getMaxX();
				double pt1Y = bound.getMinY();
				double pt2X = bound.getMinX();
				double pt2Y = metBound.getMaxY();
				if (Math.sqrt((pt1X - pt2X) * (pt1X - pt2X) + (pt1Y - pt2Y) * (pt1Y - pt2Y)) > Math.max(dist[0], dist[1])) return false;
				if (pointInRTree(bTree, pt1X, pt1Y, netID, recsOnPath)) return false;
				if (pointInRTree(bTree, pt2X, pt2Y, netID, recsOnPath)) return false;
				return true;
			}
			if (metBound.getMinX() > bound.getMaxX() && metBound.getMaxY() < bound.getMinY())
			{
				// metal to lower-right of test area
				double pt1X = metBound.getMinX();
				double pt1Y = bound.getMinY();
				double pt2X = bound.getMaxX();
				double pt2Y = metBound.getMaxY();
				if (Math.sqrt((pt1X - pt2X) * (pt1X - pt2X) + (pt1Y - pt2Y) * (pt1Y - pt2Y)) > Math.max(dist[0], dist[1])) return false;
				if (pointInRTree(bTree, pt1X, pt1Y, netID, recsOnPath)) return false;
				if (pointInRTree(bTree, pt2X, pt2Y, netID, recsOnPath)) return false;
				return true;
			}
			return false;
		}

		private boolean pointInRTree(BlockageTree bTree, double x, double y, MutableInteger netID, List<Rectangle2D> recsOnPath)
		{
			Rectangle2D searchArea = new Rectangle2D.Double(x-0.5, y-0.5, 1, 1);
			for (Iterator<SOGBound> sea = bTree.search(searchArea); sea.hasNext(); )
			{
				SOGBound sBound = sea.next();
				if (!sBound.isSameBasicNet(netID)) continue;
				if (DBMath.isGreaterThan(sBound.getBounds().getMinX(), x) ||
					DBMath.isLessThan(sBound.getBounds().getMaxX(), x) ||
					DBMath.isGreaterThan(sBound.getBounds().getMinY(), y) ||
					DBMath.isLessThan(sBound.getBounds().getMaxY(), y))
						continue;
				return true;
			}

			// now see if it is on the path
			for (Rectangle2D bound : recsOnPath)
			{
				if (DBMath.isGreaterThan(bound.getMinX(), x) ||
					DBMath.isLessThan(bound.getMaxX(), x) ||
					DBMath.isGreaterThan(bound.getMinY(), y) ||
					DBMath.isLessThan(bound.getMaxY(), y))
						continue;
				return true;
			}
			return false;
		}
	}

	/************************************** SEARCH VERTICES **************************************/

	public static class OrderedSearchVertex
	{
		TreeMap<Integer,List<SearchVertex>> listBetter;

		OrderedSearchVertex()
		{
			listBetter = new TreeMap<Integer,List<SearchVertex>>();
		}

		public Set<SearchVertex> getSet()
		{
			Set<SearchVertex> totalList = new TreeSet<SearchVertex>();
			for(Integer key : listBetter.keySet())
			{
				List<SearchVertex> curList = listBetter.get(key);
				for(SearchVertex sv : curList) totalList.add(sv);
			}
			return totalList;
		}

		public void add(SearchVertex sv)
		{
			Integer key = Integer.valueOf(sv.cost);
			List<SearchVertex> curList = listBetter.get(key);
			if (curList == null) listBetter.put(key, curList = new ArrayList<SearchVertex>());
			curList.add(sv);
		}

		public void remove(SearchVertex sv)
		{
			Integer key = Integer.valueOf(sv.cost);
			List<SearchVertex> curList = listBetter.get(key);
			if (curList != null)
			{
				curList.remove(sv);
				if (curList.size() == 0) listBetter.remove(key);
			} else
				System.out.println("++++++++++ COULD NOT REMOVE SEARCH VERTEX");
		}

		public boolean inList(SearchVertex sv)
		{
			Integer key = Integer.valueOf(sv.cost);
			List<SearchVertex> curList = listBetter.get(key);
			if (curList == null) return false;
			return curList.contains(sv);
		}

		public SearchVertex getFirst()
		{
			if (listBetter.size() == 0) return null;
			Integer key = listBetter.firstKey();
			List<SearchVertex> curList = listBetter.get(key);
			if (curList.size() == 0) System.out.println("+++++++++++++ HMMM, FIRST KEY HAS NOTHING ("+key+")");
			return curList.get(0);
		}
	}

	/**
	 * Class to define a vertex in the Dijkstra search.
	 */
	public static class SearchVertex implements Comparable<SearchVertex>
	{
		/** the coordinate of the search vertex. */		private final double xv, yv;
		/** the layer of the search vertex. */			private final int zv;
		/** the cost of search to this vertex. */		private int cost;
		/** the layer of cuts in "cuts". */				private int cutLayer;
		/** auto-generation of intermediate vertices */	private int autoGen;
		/** index of global-routing bucket progress */	private int globalRoutingBucket;
		/** the cuts in the contact. */					private Point2D[] cuts;
		/** the size of the contact. */					private Point2D size;
		/** the previous vertex in the search. */		private SearchVertex last;
		/** the routing state. */						private final Wavefront wf;

		/**
		 * Method to create a new SearchVertex.
		 * @param x the X coordinate of the SearchVertex.
		 * @param y the Y coordinate of the SearchVertex.
		 * @param z the Z coordinate (metal layer) of the SearchVertex.
		 * @param whichContact the contact number to use if switching layers.
		 * @param cuts an array of cuts in this contact (if switching layers).
		 * @param cl the layer of the cut (if switching layers).
		 * @param w the Wavefront that this SearchVertex is part of.
		 */
		SearchVertex(double x, double y, int z, int whichContact, Point2D[] cuts, Point2D size, int cl, Wavefront w)
		{
			xv = x;
			yv = y;
			zv = (z << 8) + (whichContact & 0xFF);
			this.cuts = cuts;
			this.size = size;
			cutLayer = cl;
			autoGen = -1;
			globalRoutingBucket = -1;
			wf = w;
		}

		public double getX() { return xv; }

		public double getY() { return yv; }

		public int getZ() { return zv >> 8; }

		public SearchVertex getLast() { return last; }

		public int getCost() { return cost; }

		public int getGRBucket() { return globalRoutingBucket; }

		public Wavefront getWavefront() { return wf; }

		int getContactNo() { return zv & 0xFF; }

		Point2D[] getCuts() { return cuts; }

		Point2D getSize() { return size; }

		void clearCuts() { cuts = null; }

		int getCutLayer() { return cutLayer; }

		int getAutoGen() { return autoGen; }

		/**
		 * Method to set the automatic generation of intermediate SearchVertex objects.
		 * When a SearchVertex is placed at a great distance from the previous one,
		 * it may have missed intermediate steps.
		 * The AutoGen value directs creation of an intermediate step.
		 * @param a negative to ignore, otherwise is the direction index to automatically generate intermediate steps.
		 */
		void setAutoGen(int a) { autoGen = a; }

		/**
		 * Method to sort SearchVertex objects by their cost.
		 */
		public int compareTo(SearchVertex svo)
		{
			int diff = cost - svo.cost;
			if (diff != 0) return diff;
			if (wf != null)
			{
				double thisDist = Math.abs(xv - wf.toX) + Math.abs(yv - wf.toY) + Math.abs(zv - wf.toZ);
				double otherDist = Math.abs(svo.xv - wf.toX) + Math.abs(svo.yv - wf.toY) + Math.abs(svo.zv - wf.toZ);
				if (thisDist < otherDist) return -1;
				if (thisDist > otherDist) return 1;
			}
			return 0;
		}

		private void generateIntermediateVertex(NeededRoute nr, int lastDirection, FixpRectangle toRectGridded)
		{
			SearchVertex prevSV = last;
			double dX = 0, dY = 0;
			if (getX() > prevSV.getX()) dX = -1; else
				if (getX() < prevSV.getX()) dX = 1; else
					if (getY() > prevSV.getY()) dY = -1; else
						if (getY() < prevSV.getY()) dY = 1;
			if (dX == 0 && dY == 0) return;
			int z = getZ();
			double newX = getX();
			double newY = getY();
			if (dX < 0)
			{
				newX -= GRAINSIZE;
				if (!inDestGrid(toRectGridded, newX, newY)) newX = nr.getLowerXGrid(z, newX);
			}
			if (dX > 0)
			{
				newX += GRAINSIZE;
				if (!inDestGrid(toRectGridded, newX, newY)) newX = nr.getUpperXGrid(z, newX);
			}
			if (dY < 0)
			{
				newY -= GRAINSIZE;
				if (!inDestGrid(toRectGridded, newX, newY)) newY = nr.getLowerYGrid(z, newY);
			}
			if (dY > 0)
			{
				newY += GRAINSIZE;
				if (!inDestGrid(toRectGridded, newX, newY)) newY = nr.getUpperYGrid(z, newY);
			}

			for(;;)
			{
				if (dX < 0 && newX <= prevSV.getX()) break;
				if (dX > 0 && newX >= prevSV.getX()) break;
				if (dY < 0 && newY <= prevSV.getY()) break;
				if (dY > 0 && newY >= prevSV.getY()) break;
				if (wf.getVertex(newX, newY, z) == null)
				{
					SearchVertex svIntermediate = new SearchVertex(newX, newY, z, getContactNo(), getCuts(), getSize(), z, wf);
					if (wf.globalRoutingDelta != 0)
						svIntermediate.globalRoutingBucket = wf.getNextBucket(this, newX, newY);
					svIntermediate.setAutoGen(lastDirection);
					svIntermediate.last = prevSV;
					svIntermediate.cost = cost + 1;
					wf.setVertex(newX, newY, z, svIntermediate);
					wf.active.add(svIntermediate);
					break;
				}
				if (dX < 0)
				{
					newX -= GRAINSIZE;
					if (!inDestGrid(toRectGridded, newX, newY)) newX = nr.getLowerXGrid(z, newX);
				}
				if (dX > 0)
				{
					newX += GRAINSIZE;
					if (!inDestGrid(toRectGridded, newX, newY)) newX = nr.getUpperXGrid(z, newX);
				}
				if (dY < 0)
				{
					newY -= GRAINSIZE;
					if (!inDestGrid(toRectGridded, newX, newY)) newY = nr.getLowerYGrid(z, newY);
				}
				if (dY > 0)
				{
					newY += GRAINSIZE;
					if (!inDestGrid(toRectGridded, newX, newY)) newY = nr.getUpperYGrid(z, newY);
				}
			}
		}
	}

	/********************************* INITIALIZATION *********************************/

	/**
	 * Method to initialize technology information, including design rules.
	 * @return true on error.
	 */
	private boolean initializeDesignRules()
	{
		// find the metal layers, arcs, and contacts
		numMetalLayers = tech.getNumMetals();
		metalLayers = new Layer[numMetalLayers];
		metalArcs = new ArcProto[numMetalLayers];
		favorArcs = new boolean[numMetalLayers];
		preventArcs = new boolean[numMetalLayers];
		viaLayers = new Layer[numMetalLayers - 1];
		metalVias = new MetalVias[numMetalLayers - 1];
		for (int i = 0; i < numMetalLayers - 1; i++)
			metalVias[i] = new MetalVias();
		for (Iterator<Layer> it = tech.getLayers(); it.hasNext();)
		{
			Layer lay = it.next();
			if (!lay.getFunction().isMetal()) continue;
			int layerIndex = lay.getFunction().getLevel() - 1;
			if (layerIndex < numMetalLayers)  metalLayers[layerIndex] = lay;
		}
		boolean hasFavorites = false;
		for (Iterator<ArcProto> it = tech.getArcs(); it.hasNext(); )
		{
			ArcProto ap = it.next();
			for (int i = 0; i < numMetalLayers; i++)
			{
				if (ap.getLayer(0) == metalLayers[i])
				{
					metalArcs[i] = ap;
					favorArcs[i] = sogp.isFavored(ap);
					if (favorArcs[i]) hasFavorites = true;
					preventArcs[i] = sogp.isPrevented(ap);
					break;
				}
			}
		}
		if (!hasFavorites)
			for (int i = 0; i < numMetalLayers; i++) favorArcs[i] = true;

		// regular expression to discard some primitives
		String ignorePattern = sogp.getIgnorePrimitives();		// "Z-(\\w+)"
		String acceptPattern = sogp.getAcceptOnlyPrimitives();	// "X-(\\w+)"
		boolean contactsCanRotate = sogp.isContactsRotate();
		Pattern ignorePat = (ignorePattern != null) ? Pattern.compile(ignorePattern) : null;
		Pattern acceptPat = (acceptPattern != null) ? Pattern.compile(acceptPattern) : null;
		for (Iterator<PrimitiveNode> it = tech.getNodes(); it.hasNext();)
		{
			PrimitiveNode np = it.next();
			if (np.isNotUsed()) continue;
			if (!np.getFunction().isContact()) continue;

			if (ignorePat != null)
			{
				// Match found and therefore not using this primitive
				Matcher matcher = ignorePat.matcher(np.getName());
				if (matcher.find()) continue;
			}

			if (acceptPat != null)
			{
				// Match not found and therefore not using this primitive
				Matcher matcher = acceptPat.matcher(np.getName());
				if (!matcher.find()) continue;
			}

			ArcProto[] conns = np.getPort(0).getConnections();
			for (int i = 0; i < numMetalLayers - 1; i++)
			{
				if ((conns[0] == metalArcs[i] && conns[1] == metalArcs[i + 1]) ||
					(conns[1] == metalArcs[i] && conns[0] == metalArcs[i + 1]))
				{
					// see if the node is asymmetric and should exist in rotated states
					boolean square = true, offCenter = false;
					NodeInst dummyNi = NodeInst.makeDummyInstance(np, ep);
					Poly[] conPolys = tech.getShapeOfNode(dummyNi);
					int horMet = -1, verMet = -1;
					double horMetInset = 0, verMetInset = 0;
					for (int p = 0; p < conPolys.length; p++)
					{
						Poly conPoly = conPolys[p];
						Layer conLayer = conPoly.getLayer();
						Layer.Function lFun = conLayer.getFunction();
						if (lFun.isMetal())
						{
							Rectangle2D conRect = conPoly.getBounds2D();
							if (conRect.getWidth() != conRect.getHeight())
							{
								square = false;
								if (conRect.getWidth() > conRect.getHeight())
								{
									// horizontal piece
									horMet = lFun.getLevel() - 1;
									horMetInset = dummyNi.getYSize() - conRect.getHeight();
								} else
								{
									// vertical piece
									verMet = lFun.getLevel() - 1;
									verMetInset = dummyNi.getXSize() - conRect.getWidth();
								}
							}
							if (conRect.getCenterX() != 0 || conRect.getCenterY() != 0) offCenter = true;
						} else if (lFun.isContact())
						{
							viaLayers[i] = conLayer;
						}
					}

					if (square || offCenter) horMet = verMet = -1; else
						if (horMet < 0 || verMet < 0) horMet = verMet = -1;
					metalVias[i].addVia(np, 0, horMet, horMetInset, verMet, verMetInset);
					if (contactsCanRotate)
					{
						if (offCenter)
						{
							// off center: test in all 4 rotations
							metalVias[i].addVia(np, 90, horMet, horMetInset, verMet, verMetInset);
							metalVias[i].addVia(np, 180, horMet, horMetInset, verMet, verMetInset);
							metalVias[i].addVia(np, 270, horMet, horMetInset, verMet, verMetInset);
						} else if (!square)
						{
							// centered but not square: test in 90-degree rotation
							metalVias[i].addVia(np, 90, verMet, verMetInset, horMet, horMetInset);
						}
					}
					break;
				}
			}
		}
		for (int i = 0; i < numMetalLayers; i++)
		{
			if (metalLayers[i] == null)
			{
				error("Cannot find layer for Metal " + (i + 1));
				return true;
			}
			if (metalArcs[i] == null)
			{
				error("Cannot find arc for Metal " + (i + 1));
				return true;
			}
			if (i < numMetalLayers - 1)
			{
				boolean noContact = false;
				if (metalVias[i].getVias().size() == 0)
				{
					warn("Cannot find contact node between Metal " + (i+1) + " and Metal " + (i+2) + " in technology " + tech.getTechName() +
						". Ignoring Metal layers " + (i+2) + " and higher.");
					noContact = true;
				} else if (viaLayers[i] == null)
				{
					warn("Cannot find contact layer between Metal " + (i+1) + " and Metal " + (i+2) + " in technology " + tech.getTechName() +
						". Ignoring Metal layers " + (i+2) + " and higher.");
					noContact = true;
				}
				if (noContact)
				{
					for(int j=i+1; j<numMetalLayers; j++)
						preventArcs[j] = true;
					break;
				}
			}
		}

		// compute design rule spacings
		layerSurround = new Map[numMetalLayers];
		for (int i = 0; i < numMetalLayers; i++)
			layerSurround[i] = new HashMap<Double, Map<Double, double[]>>();
		metalSurround = new double[numMetalLayers];
		worstMetalSurround = new double[numMetalLayers];
		MutableDouble mutableDist = new MutableDouble(0);
		for (int i = 0; i < numMetalLayers; i++)
		{
			DRCTemplate rule = DRC.getSpacingRule(metalLayers[i], null, metalLayers[i], null, false, -1, metalArcs[i].getDefaultLambdaBaseWidth(ep), 50);
			metalSurround[i] = 0;
			if (rule != null) metalSurround[i] = rule.getValue(0);
			if (DRC.getMaxSurround(metalLayers[i], Double.MAX_VALUE, mutableDist)) // only when found
				worstMetalSurround[i] = mutableDist.doubleValue();
		}
		viaSurround = new double[numMetalLayers - 1];
		for (int i = 0; i < numMetalLayers - 1; i++)
		{
			Layer lay = viaLayers[i];
			if (lay == null) continue;

			double spacing = 2;
			double arcWidth = metalArcs[i].getDefaultLambdaBaseWidth(ep);
			DRCTemplate ruleSpacing = DRC.getSpacingRule(lay, null, lay, null, false, -1, arcWidth, 50);
			if (ruleSpacing != null) spacing = ruleSpacing.getValue(0);

			// determine cut size
			double width = 0;
			DRCTemplate ruleWidth = DRC.getMinValue(lay, DRCTemplate.DRCRuleType.NODSIZ);
			if (ruleWidth != null) width = ruleWidth.getValue(0);

			// extend to the size of the largest cut
			List<MetalVia> nps = metalVias[i].getVias();
			for (MetalVia mv : nps)
			{
				NodeInst dummyNi = NodeInst.makeDummyInstance(mv.via, ep);
				Poly[] conPolys = tech.getShapeOfNode(dummyNi);
				for (int p = 0; p < conPolys.length; p++)
				{
					Poly conPoly = conPolys[p];
					if (conPoly.getLayer().getFunction().isContact())
					{
						Rectangle2D bounds = conPoly.getBounds2D();
						width = Math.max(width, bounds.getWidth());
						width = Math.max(width, bounds.getHeight());
					}
				}
			}

			viaSurround[i] = spacing + width;
		}
		return false;
	}

// debugging code
//	private void dumpSpacing()
//	{
//		System.out.println("CACHED METAL SPACINGS");
//		for(int lay=0; lay<numMetalLayers; lay++)
//		{
//			System.out.println("LAYER "+(lay+1));
//			for(Double wid : layerSurround[lay].keySet())
//			{
//				Map<Double, Double> widMap = layerSurround[lay].get(wid);
//				System.out.println("   WIDTH "+wid);
//				Map<Double,Set<Double>> found = new TreeMap<Double,Set<Double>>();
//				for(Double len : widMap.keySet())
//				{
//					Double dist = widMap.get(len);
//					Set<Double> lengths = found.get(dist);
//					if (lengths == null) found.put(dist, lengths = new TreeSet<Double>());
//					lengths.add(len);
//				}
//				for(Double dist : found.keySet())
//				{
//					boolean foundOne = false;
//					double low = 0, high = 0;
//					for(Double len : found.get(dist))
//					{
//						if (foundOne)
//						{
//							if (len.doubleValue() < low) low = len.doubleValue();
//							if (len.doubleValue() > high) high = len.doubleValue();
//						} else
//						{
//							foundOne = true;
//							low = high = len.doubleValue();
//						}
//					}
//					System.out.println("      LENGTHS "+low+" TO "+high+" HAVE SPACING "+dist);
//				}
//			}
//		}
//	}

	private void initializeGrids()
	{
		metalGrid = new double[numMetalLayers][];
		metalGridRange = new double[numMetalLayers];
		for(int i=0; i<numMetalLayers; i++)
		{
			ArcProto ap = metalArcs[i];
			String arcGrid = sogp.getGrid(ap);
			if (arcGrid == null)
			{
				metalGrid[i] = null;
				metalGridRange[i] = 0;
			} else
			{
				List<Double> found = new ArrayList<Double>();
				String[] parts = arcGrid.split(",");
				for(int j=0; j<parts.length; j++)
				{
					String part = parts[j].trim();
					if (part.length() == 0) continue;
					double val = TextUtils.atof(part);
					found.add(new Double(val));
				}
				metalGrid[i] = new double[found.size()];
				for(int j=0; j<found.size(); j++) metalGrid[i][j] = found.get(j).doubleValue();
				metalGridRange[i] = metalGrid[i][found.size()-1] - metalGrid[i][0];
			}
		}
	}

	private static class RoutesOnNetwork implements Comparable<RoutesOnNetwork>
	{
		String netName;
		List<SteinerTreePortPair> pairs;
		List<PortInst> spineTapPorts;
		List<ArcInst> unroutedArcs;
		List<PortInst> unorderedPorts;
		List<NeededRoute> neededRoutes;

		RoutesOnNetwork(String netName)
		{
			this.netName = netName;
			pairs = new ArrayList<SteinerTreePortPair>();
			spineTapPorts = null;
			unroutedArcs = new ArrayList<ArcInst>();
			unorderedPorts = new ArrayList<PortInst>();
			neededRoutes = new ArrayList<NeededRoute>();
		}

		public void addUnorderedPort(PortInst pi)
		{
			if (pi.getNodeInst().isCellInstance() ||
				((PrimitiveNode)pi.getNodeInst().getProto()).getTechnology() != Generic.tech())
			{
				if (!unorderedPorts.contains(pi)) unorderedPorts.add(pi);
			}
		}

		/**
		 * Method to setup a spine network (many colinear points).
		 * @return true if this is a spine network.
		 */
		public boolean setupSpineInfo()
		{
			// first make a set of unique ports (remove duplicates)
			Set<PortInst> uniquePorts = new HashSet<PortInst>();
			for(PortInst pi : unorderedPorts)
				uniquePorts.add(pi);

			// nets with 3 or less points are not spines
			if (uniquePorts.size() < 4) return false;

			// get bounds of the points
			double lowX = 0, highX = 0, lowY = 0, highY = 0;
			boolean first = true;
			for(PortInst pi : uniquePorts)
			{
				EPoint pt = pi.getCenter();
				if (first)
				{
					lowX = highX = pt.getX();
					lowY = highY = pt.getY();
					first = false;
				} else
				{
					if (pt.getX() < lowX) lowX = pt.getX();
					if (pt.getX() > highX) highX = pt.getX();
					if (pt.getY() < lowY) lowY = pt.getY();
					if (pt.getY() > highY) highY = pt.getY();
				}
			}

			// see if this looks like a spine
			double width = highX - lowX, height = highY - lowY;
			if (width*SPINERATIO > height && height*SPINERATIO > width) return false;
			PortInst endA = null, endB = null;
			if (width*SPINERATIO <= height)
			{
				// vertical spine, find the endpoints
				double lowestY = 0, highestY = 0;
				first = true;
				for(PortInst pi : uniquePorts)
				{
					EPoint pt = pi.getCenter();
					if (first)
					{
						lowestY = highestY = pt.getY();
						endA = endB = pi;
						first = false;
					} else
					{
						if (pt.getY() < lowestY)  { lowestY  = pt.getX();  endA = pi; }
						if (pt.getY() > highestY) { highestY = pt.getX();  endB = pi; }
					}
				}
			} else
			{
				// horizontal spine, find the endpoints
				double lowestX = 0, highestX = 0;
				first = true;
				for(PortInst pi : uniquePorts)
				{
					EPoint pt = pi.getCenter();
					if (first)
					{
						lowestX = highestX = pt.getX();
						endA = endB = pi;
						first = false;
					} else
					{
						if (pt.getX() < lowestX)  { lowestX  = pt.getX();  endA = pi; }
						if (pt.getX() > highestX) { highestX = pt.getX();  endB = pi; }
					}
				}
			}
			if (endA == null || endB == null || endA == endB) return false;

			// this is a spine: add non-endpoints to the tap list
			spineTapPorts = new ArrayList<PortInst>();
			for(PortInst pi : uniquePorts)
			{
				if (pi != endA && pi != endB) spineTapPorts.add(pi);
			}
			pairs.add(new SteinerTreePortPair(endA, endB));
			return true;
		}

		/**
		 * Method to sort RoutesOnNetwork by their network name.
		 */
		public int compareTo(RoutesOnNetwork other) { return netName.compareTo(other.netName); }
	}

	private RouteBatch[] makeListOfRoutes(Netlist netList, List<ArcInst> arcsToRoute, List<EPoint> linesInNonMahnattan)
	{
		List<RoutesOnNetwork> allRoutes = new ArrayList<RoutesOnNetwork>();
		if (!DEBUGGRIDDING && RoutingDebug.isActive() && !RoutingDebug.isRewireNetworks())
		{
			// make a batch with just the first arc
			ArcInst ai = arcsToRoute.get(0);
			Network net = netList.getNetwork(ai, 0);
			RoutesOnNetwork ron = new RoutesOnNetwork(net.getName());
			allRoutes.add(ron);
			ron.unroutedArcs.add(ai);
			ron.addUnorderedPort(ai.getHeadPortInst());
			ron.addUnorderedPort(ai.getTailPortInst());
			ron.pairs.add(new SteinerTreePortPair(ai.getHeadPortInst(), ai.getTailPortInst()));
		} else
		{
			// organize arcs by network
			Map<Network,List<ArcInst>> seen = new HashMap<Network,List<ArcInst>>();
			for (int b = 0; b < arcsToRoute.size(); b++)
			{
				// get list of PortInsts that comprise this net
				ArcInst ai = arcsToRoute.get(b);
				Network net = netList.getNetwork(ai, 0);
				if (net == null)
				{
					warn("Arc " + describe(ai) + " has no network!");
					continue;
				}
				List<ArcInst> arcsOnNet = seen.get(net);
				if (arcsOnNet == null) seen.put(net, arcsOnNet = new ArrayList<ArcInst>());
				arcsOnNet.add(ai);
			}

			// build a RoutesOnNetwork for every network
			List<Network> allNets = new ArrayList<Network>();
			for(Network net : seen.keySet()) allNets.add(net);
			Collections.sort(allNets);
			for(Network net : allNets)
			{
				RoutesOnNetwork ron = new RoutesOnNetwork(net.getName());
				allRoutes.add(ron);

				// make list of all ends in the batch
				List<ArcInst> arcsOnNet = seen.get(net);
				for (ArcInst ai : arcsOnNet)
				{
					ron.unroutedArcs.add(ai);
					ron.addUnorderedPort(ai.getHeadPortInst());
					ron.addUnorderedPort(ai.getTailPortInst());
				}

				if (sogp.isSteinerDone())
				{
					for (ArcInst ai : arcsOnNet)
						ron.pairs.add(new SteinerTreePortPair(ai.getHeadPortInst(), ai.getTailPortInst()));
				} else
				{
					// see if this is a spine route
					if (prefs.enableSpineRouting && ron.setupSpineInfo())
					{
						// a spine route
					} else
					{
						// a non-spine route
						List<SteinerTreePort> portList = new ArrayList<SteinerTreePort>();
						for(PortInst pi : ron.unorderedPorts) portList.add(new PortInstShadow(pi));
						SteinerTree st = new SteinerTree(portList);
						ron.pairs = st.getTreeBranches();
						if (ron.pairs == null)
						{
							String errMsg = "Arcs in " + net.getName() + " do not make valid connection: deleted";
							error(errMsg);
							List<EPoint> lineList = new ArrayList<EPoint>();
							for(ArcInst delAi : ron.unroutedArcs)
							{
								lineList.add(delAi.getHeadLocation());
								lineList.add(delAi.getTailLocation());
							}
							errorLogger.logMessageWithLines(errMsg, null, lineList, cell, 0, true);
						}
					}
				}
			}
		}

		// build the R-Tree
		if (buildRTrees(netList, arcsToRoute, linesInNonMahnattan))
		{
			info("Non-Manhattan geometry found");
//			hadNonmanhattan.setValue(true);
		}

		// make NeededRoute objects and fill-in network information in the R-Tree
		totalBlockages = getNumBlockages();
		blockagesFound = 0;
		setProgressNote("Extract connectivity (" + totalBlockages + " blockages)");
		setProgressValue(0, 100);

		for(RoutesOnNetwork ron : allRoutes)
		{
			// determine the minimum width of arcs on this net
			double minWidth = getMinWidth(ron.unorderedPorts);

			for(int i=0; i<ron.pairs.size(); i++)
			{
				Object obj1 = ron.pairs.get(i).getPort1();
				if (obj1 instanceof PortInstShadow) obj1 = ((PortInstShadow)obj1).getPortInst();
				Object obj2 = ron.pairs.get(i).getPort2();
				if (obj2 instanceof PortInstShadow) obj2 = ((PortInstShadow)obj2).getPortInst();
				PortInst aPi = (PortInst)obj1;
				PortInst bPi = (PortInst)obj2;

				// get Arc information about the ends of the path
				ArcProto aArc = getMetalArcOnPort(aPi);
				if (aArc == null) continue;
				ArcProto bArc = getMetalArcOnPort(bPi);
				if (bArc == null) continue;

				// create the NeededRoute and the blockages around it
				NeededRoute nr = new NeededRoute(ron.netName, aPi, bPi, aArc, bArc, ron.spineTapPorts, minWidth);
				if (inValidPort(aPi, nr) || inValidPort(bPi, nr)) continue;
				ron.neededRoutes.add(nr);

				Iterator<ArcInst> it = ron.unroutedArcs.iterator();
				ArcInst sampleArc = it.next();
				Network net = netList.getNetwork(sampleArc, 0);
				nr.setNetID(net);
				nr.growNetwork();
			}
		}

		// merge RoutesOnNetwork that are on the same network
		setProgressNote("Final Prepare for Routing...");
		setProgressValue(0, 100);
		Map<Integer,Set<RoutesOnNetwork>> uniqueNets = new TreeMap<Integer,Set<RoutesOnNetwork>>();
		for(RoutesOnNetwork ron : allRoutes)
		{
			int netNumber = -1;
			for(NeededRoute nr : ron.neededRoutes)
			{
				int thisNetNumber = nr.netID.intValue();
				if (netNumber < 0) netNumber = thisNetNumber;
				if (netNumber != thisNetNumber)
					error("Error: network " + ron.netName + " has network IDs " + netNumber + " and " + thisNetNumber);
			}
			Integer id = Integer.valueOf(netNumber);
			Set<RoutesOnNetwork> mergedRoutes = uniqueNets.get(id);
			if (mergedRoutes == null) uniqueNets.put(id, mergedRoutes = new TreeSet<RoutesOnNetwork>());
			mergedRoutes.add(ron);
		}

		// build RouteBatch objects for merged RoutesOnNetwork
		List<RouteBatch> allBatches = new ArrayList<RouteBatch>();
		for(Integer netID : uniqueNets.keySet())
		{
			Set<RoutesOnNetwork> mergedRoutes = uniqueNets.get(netID);
			RouteBatch rb = null;
			for(RoutesOnNetwork ron : mergedRoutes)
			{
				if (rb == null)
				{
					rb = new RouteBatch();
					allBatches.add(rb);
				}
				for(ArcInst ai : ron.unroutedArcs)
				{
					rb.unroutedArcs.add(ai);
					if (!rb.isPwrGnd)
					{
						Network net = netList.getNetwork(ai, 0);
						for (Iterator<Export> it = net.getExports(); it.hasNext();)
						{
							Export e = it.next();
							if (e.isGround() || e.isPower())
							{
								rb.isPwrGnd = true;
								break;
							}
						}
						PortProto headPort = ai.getHeadPortInst().getPortProto();
						PortProto tailPort = ai.getTailPortInst().getPortProto();
						if (headPort.isGround() || headPort.isPower() || tailPort.isGround() || tailPort.isPower()) rb.isPwrGnd = true;
					}
				}
				for(NeededRoute nr : ron.neededRoutes)
				{
					nr.setBatchInfo(rb, rb.routesInBatch.size());
					double dX = nr.getAX() - nr.getBX();
					double dY = nr.getAY() - nr.getBY();
					rb.length += Math.sqrt(dX*dX + dY*dY);

					// put the NeededRoute in the batch
					rb.addRoute(nr);
				}
			}
		}

		// add blockages around port areas
		for(RouteBatch rb : allBatches)
		{
			for(NeededRoute nr : rb.routesInBatch)
			{
				nr.addBlockagesAtPorts(nr.aPi);
				nr.addBlockagesAtPorts(nr.bPi);
				if (nr.spineTaps != null)
					for(PortInst pi : nr.spineTaps)
						nr.addBlockagesAtPorts(pi);
			}
		}

		// sort batches
   		Collections.sort(allBatches);

		RouteBatch[] routeBatches = new RouteBatch[allBatches.size()];
		for(int i=0; i<allBatches.size(); i++)
			routeBatches[i] = allBatches.get(i);
		return routeBatches;
	}

	private ArcProto getMetalArcOnPort(PortInst pi)
	{
		ArcProto[] arcs = getPossibleConnections(pi.getPortProto());
		for (int j = 0; j < arcs.length; j++)
		{
			if (arcs[j].getTechnology() == tech && arcs[j].getFunction().isMetal()) return arcs[j];
		}

		String errorMsg = "Cannot connect port " + pi.getPortProto().getName() + " of node " +
			describe(pi.getNodeInst()) + " because it has no metal connection in " + tech.getTechName() + " technology";
		error(errorMsg);
		List<PolyBase> polyList = new ArrayList<PolyBase>();
		polyList.add(pi.getPoly());
		errorLogger.logMessage(errorMsg, polyList, cell, 0, true);
		return null;
	}

	/**
	 * Method to find a path between two ports.
	 * @param nr the NeededRoute object with all necessary information.
	 * If successful, the NeededRoute's "vertices" field is filled with the route data.
	 */
	Runnable[] findPath(NeededRoute nr)
	{
		// prepare the routing path
		Wavefront[] wavefronts = nr.makeWavefronts();

		if (nr.checkEndSurround()) return null;

		// special case when route is null length
		Wavefront d1 = wavefronts[0];
		Wavefront d2 = wavefronts[1];
		if (DBMath.rectsIntersect(d1.fromRect, d1.toRect) && d1.toZ == d1.fromZ)
		{
			double xVal = (Math.max(d1.fromRect.getMinX(), d1.toRect.getMinX()) + Math.min(d1.fromRect.getMaxX(), d1.toRect.getMaxX())) / 2;
			double yVal = (Math.max(d1.fromRect.getMinY(), d1.toRect.getMinY()) + Math.min(d1.fromRect.getMaxY(), d1.toRect.getMaxY())) / 2;
			SearchVertex sv = new SearchVertex(xVal, yVal, d1.toZ, 0, null, null, 0, d1);
			nr.completeRoute(sv);
			return null;
		}

		if (parallelDij)
		{
			DijkstraParallel aToB = new DijkstraParallel(d1, d2);
			DijkstraParallel bToA = new DijkstraParallel(d2, d1);
			return new Runnable[] { aToB, bToA };
		} else {
			return new Runnable[] { new DijkstraTwoWay(nr, d1, d2) };
		}
	}

	/********************************* SEARCH SUPPORT *********************************/

	private class DijkstraParallel implements Runnable {
		private final Wavefront wf;
		private final Wavefront otherWf;

		private DijkstraParallel(Wavefront wf, Wavefront otherWf) {
			this.wf = wf;
			this.otherWf = otherWf;
		}

		@Override
		public void run() {
			Environment.setThreadEnvironment(env);

			// run the wavefront to the end
			SearchVertex result = null;
			while (result == null)
			{
				if (wf.abort) result = svAbandoned; else
					result = wf.advanceWavefront();
			}

			boolean success = result.wf != null;
			if (success) {
				assert result.wf == wf;
			}

			synchronized (wf.nr) {
				assert !wf.finished;
				if (otherWf.finished) {
				} else {
					otherWf.abort = true;
					if (success) {
						wf.nr.routedSuccess = true; // Mark in lock
					}
				}
				wf.finished = true;
			}
			wf.nr.completeRoute(result);
		}
	}

	/**
	 * Class to run search from both ends at once (interleaved).
	 * Used in single-processor systems.
	 */
	private class DijkstraTwoWay implements Runnable {
		private final NeededRoute nr;
		private final Wavefront dirAtoB, dirBtoA;

		/**
		 * @param nr the NeededRoute to search.
		 * @param dirAtoB Wavefront from A to B
		 * @param dirBtoA Wavefront from B to A
		 */
		private DijkstraTwoWay(NeededRoute nr, Wavefront dirAtoB, Wavefront dirBtoA) {
			this.nr = nr;
			this.dirAtoB = dirAtoB;
			this.dirBtoA = dirBtoA;
		}

		@Override
		public void run() {
			Environment.setThreadEnvironment(env);

			// run both wavefronts in parallel (interleaving steps)
			SearchVertex result = null;
			while (result == null)
			{
				SearchVertex resultA = dirAtoB.advanceWavefront();
				SearchVertex resultB = dirBtoA.advanceWavefront();
				if (resultA != null || resultB != null)
				{
					if (resultA == svAborted || resultB == svAborted)
					{
						nr.completeRoute(svAborted);
						return;
					}
					if (resultA == svLimited || resultB == svLimited)
					{
						nr.completeRoute(svLimited);
						return;
					}
					if (resultA == svExhausted || resultB == svExhausted)
					{
						nr.completeRoute(svExhausted);
						return;
					}
					result = resultA;
					if (result == null || result == svAbandoned)
					{
						result = resultB;
					}
				}
			}

			nr.completeRoute(result);
		}
	}

	/********************************* MISCELLANEOUS SUPPORT *********************************/

	private double getMinWidth(List<PortInst> orderedPorts)
	{
		double minWidth = 0;
		for (PortInst pi : orderedPorts)
		{
			double widestAtPort = getWidestMetalArcOnPort(pi);
			if (widestAtPort > minWidth) minWidth = widestAtPort;
		}
		if (minWidth > prefs.maxArcWidth) minWidth = prefs.maxArcWidth;
		return minWidth;
	}

	/**
	 * Method to find the possible ArcProtos that can connect to a PortProto.
	 * Considers special variables on Exports that specify the possibilities.
	 * @param pp the PortProto to examine.
	 * @return an array of possible ArcProtos that can connect.
	 */
	private ArcProto[] getPossibleConnections(PortProto pp)
	{
		ArcProto[] poss = pp.getBasePort().getConnections();
		if (pp instanceof Export)
		{
			Export e = (Export)pp;
			Variable var = e.getVar(Export.EXPORT_PREFERRED_ARCS);
			if (var != null)
			{
				String[] arcNames = (String[])var.getObject();
				ArcProto[] arcs = new ArcProto[arcNames.length];
				boolean allFound = true;
				for(int j=0; j<arcNames.length; j++)
				{
					arcs[j] = ArcProto.findArcProto(arcNames[j]);
					if (arcs[j] == null) allFound = false;
				}
				if (allFound) return arcs;
			}
		}
		return poss;
	}

	private static boolean inDestGrid(FixpRectangle toRectGridded, double x, double y)
	{
		if (x < toRectGridded.getMinX()) return false;
		if (x > toRectGridded.getMaxX()) return false;
		if (y < toRectGridded.getMinY()) return false;
		if (y > toRectGridded.getMaxY()) return false;
		return true;
	}

	/**
	 * Get the widest metal arc already connected to a given PortInst. Looks
	 * recursively down the hierarchy.
	 * @param pi the PortInst to connect.
	 * @return the widest metal arc connect to that port (zero if none)
	 */
	private double getWidestMetalArcOnPort(PortInst pi)
	{
		// first check the top level
		double width = 0;
		for (Iterator<Connection> it = pi.getConnections(); it.hasNext();)
		{
			Connection c = it.next();
			ArcInst ai = c.getArc();
			if (!ai.getProto().getFunction().isMetal()) continue;
			double newWidth = ai.getLambdaBaseWidth();
			if (newWidth > width) width = newWidth;
		}

		// now recurse down the hierarchy
		NodeInst ni = pi.getNodeInst();
		if (ni.isCellInstance())
		{
			Export export = (Export)pi.getPortProto();
			PortInst exportedInst = export.getOriginalPort();
			double width2 = getWidestMetalArcOnPort(exportedInst);
			if (width2 > width) width = width2;
		}
		return width;
	}

	private boolean inValidPort(PortInst pi, NeededRoute nr)
	{
		ArcProto[] conns = getPossibleConnections(pi.getPortProto());
		boolean valid = false;
		for (int j = 0; j < conns.length; j++)
		{
			ArcProto ap = conns[j];
			if (ap.getTechnology() != tech) continue;
			if (!ap.getFunction().isMetal()) continue;
			if (nr.preventArc(conns[j].getFunction().getLevel() - 1)) continue;
			valid = true;
			break;
		}
		if (!valid)
		{
			warn("Cannot connect to port " + pi.getPortProto().getName() + " on node " +
				describe(pi.getNodeInst()) + " because all connecting layers have been prevented by Routing Preferences");
			return true;
		}
		return false;
	}

	/**
	 * Class to cache a PortInst and its center location for Steiner Tree computation.
	 */
	private static class PortInstShadow implements SteinerTreePort
	{
		private PortInst pi;
		private EPoint ctr;

		PortInstShadow(PortInst pi)
		{
			this.pi = pi;
			ctr = pi.getNodeInst().getShapeOfPort(pi.getPortProto()).getCenter();
		}

		public EPoint getCenter() { return ctr; }

		public PortInst getPortInst() { return pi; }
	}

	/**
	 * Method to convert a linked list of SearchVertex objects to an optimized path.
	 * @param initialThread the initial SearchVertex in the linked list.
	 * @return a List of SearchVertex objects optimized to consolidate runs in the X or Y axes.
	 */
	void getOptimizedList(SearchVertex initialThread, List<SearchVertex> realVertices)
	{
		realVertices.clear();
		SearchVertex thread = initialThread;
		if (thread != null)
		{
			SearchVertex lastVertex = thread;
			realVertices.add(lastVertex);
			thread = thread.last;
			while (thread != null)
			{
				if (lastVertex.getZ() != thread.getZ())
				{
					realVertices.add(thread);
					lastVertex = thread;
					thread = thread.last;
				} else
				{
					// gather a run of vertices on this layer
					double dx = thread.getX() - lastVertex.getX();
					double dy = thread.getY() - lastVertex.getY();
					lastVertex = thread;
					thread = thread.last;
					while (thread != null)
					{
						if (lastVertex.getZ() != thread.getZ()) break;
						if ((dx == 0 && thread.getX() - lastVertex.getX() != 0) ||
							(dy == 0 && thread.getY() - lastVertex.getY() != 0)) break;
						lastVertex = thread;
						thread = thread.last;
					}
					realVertices.add(lastVertex);
				}
			}
		}
	}

	/**
	 * Class to define a list of possible nodes that can connect two layers.
	 * This includes orientation.
	 */
	private static class MetalVia
	{
		PrimitiveNode via;
		int orientation;
		int horMetal, verMetal;
		double horMetalInset, verMetalInset;

		MetalVia(PrimitiveNode v, int o, int hm, double hmi, int vm, double vmi)
		{
			via = v;
			orientation = o;
			horMetal = hm;
			horMetalInset = hmi;
			verMetal = vm;
			verMetalInset = vmi;
		}
	}

	/**
	 * Class to define a list of possible nodes that can connect two layers.
	 * This includes orientation.
	 */
	private class MetalVias
	{
		List<MetalVia> vias = new ArrayList<MetalVia>();

		void addVia(PrimitiveNode pn, int o, int hm, double hmi, int vm, double vmi)
		{
			vias.add(new MetalVia(pn, o, hm, hmi, vm, vmi));
			Collections.sort(vias, new PrimsBySize());
		}

		List<MetalVia> getVias() { return vias; }
	}

	/**
	 * Comparator class for sorting primitives by their size.
	 */
	private class PrimsBySize implements Comparator<MetalVia>
	{
		/**
		 * Method to sort primitives by their size.
		 */
		public int compare(MetalVia mv1, MetalVia mv2)
		{
			PrimitiveNode pn1 = mv1.via;
			PrimitiveNode pn2 = mv2.via;
			double sz1 = pn1.getDefWidth(ep) * pn1.getDefHeight(ep);
			double sz2 = pn2.getDefWidth(ep) * pn2.getDefHeight(ep);
			if (sz1 < sz2) return -1;
			if (sz1 > sz2) return 1;
			return 0;
		}
	}

	/********************************* BLOCKAGE CLASSES *********************************/

	/**
	 * Method to return the total number of blockage objects in the Metal blockage trees.
	 * @return the total number of blockage objects in the Metal blockage trees.
	 */
	private int getNumBlockages()
	{
		int total = 0;
		for(int i=0; i<numMetalLayers; i++)
		{
			BlockageTree bTree = rTrees.getMetalTree(metalLayers[i]);
			if (bTree.root != null)
				total += getNumLeafs(bTree.root);
		}
		return total;
	}

	private int getNumLeafs(RTNode<SOGBound> branch)
	{
		int total = 0;
		for(int i=0; i<branch.getTotal(); i++)
		{
			if (branch.getFlag())
			{
				total++;
			} else
			{
                RTNode<SOGBound> subrt = branch.getChildTree(i);
                total += getNumLeafs(subrt);
			}
		}
		return total;
	}

	/**
	 * Method to build blockage R-Trees.
	 * @param netList
	 * @param arcsToRoute
	 * @return true if there is nonmanhattan geometry in the blockages (may cause problems).
	 */
	private boolean buildRTrees(Netlist netList, List<ArcInst> arcsToRoute, List<EPoint> linesInNonMahnattan)
	{
		rTrees = new BlockageTrees(numMetalLayers);

		MutableInteger nextNetNumber = new MutableInteger(1);
		Map<Network,Integer> netNumbers = new HashMap<Network,Integer>();
		for (ArcInst ai : arcsToRoute)
		{
			Network net = netList.getNetwork(ai, 0);
			Integer netNumber = netNumbers.get(net);
			if (netNumber != null) continue;

			netNumbers.put(net, netNumber = Integer.valueOf(nextNetNumber.intValue() * 8));
			nextNetNumber.increment();
			netIDs.put(net, netNumber);
		}

		// recursively add all polygons in the routing area
		setProgressNote("Find blockages...");
		setProgressValue(0, 100);
		boolean retval = addArea(cell, cellBounds, Orientation.IDENT.pureRotate(), true, nextNetNumber, linesInNonMahnattan);
		return retval;
	}

	/**
	 * Method to add geometry to the blockage R-Trees.
	 * @param cell
	 * @param bounds
	 * @param transToTop
	 * @param topLevel
	 * @param nextNetNumber
	 * @param linesInNonMahnattan List to store non-Manhattan geometry
	 * @return true if some of the geometry is nonmanhattan (and may cause problems)
	 */
	private boolean addArea(Cell cell, Rectangle2D bounds, FixpTransform transToTop, boolean topLevel,
			MutableInteger nextNetNumber, List<EPoint> linesInNonMahnattan)
	{
		// first add primitive nodes and arcs
		boolean hasNonmanhattan = false;
		int numCells = 0;
		for (Iterator<Geometric> it = cell.searchIterator(bounds); it.hasNext();)
		{
			Geometric geom = it.next();
			if (geom instanceof NodeInst)
			{
				NodeInst ni = (NodeInst)geom;
				if (ni.isCellInstance()) { numCells++;   continue; }
				FixpTransform nodeTrans = ni.rotateOut(transToTop);
				PrimitiveNode pNp = (PrimitiveNode)ni.getProto();
				Technology tech = pNp.getTechnology();
				Poly[] nodeInstPolyList = tech.getShapeOfNode(ni, true, false, null);
				MutableInteger netNumber = null;
				List<Integer> exclusionLayers = null;
				if (pNp == Generic.tech().routeNode)
				{
					Integer nn = Integer.valueOf(nextNetNumber.intValue() * 8);
					nextNetNumber.increment();
					netNumber = new MutableInteger(nn.intValue());
					exclusionLayers = parseExclusionLayers(ni);
				}
				for (int i = 0; i < nodeInstPolyList.length; i++)
				{
					PolyBase poly = nodeInstPolyList[i];
					if (exclusionLayers != null)
					{
						for(Integer lay : exclusionLayers)
						{
							poly.setLayer(getMetalLayer(lay.intValue()));
							if (addLayer(poly, nodeTrans, netNumber, false, linesInNonMahnattan, true)) hasNonmanhattan = true;
						}
					} else
					{
						if (addLayer(poly, nodeTrans, netNumber, false, linesInNonMahnattan, true)) hasNonmanhattan = true;
					}
				}
			} else
			{
				ArcInst ai = (ArcInst)geom;
				if (ai.getProto() == Generic.tech().unrouted_arc) continue;
				Technology tech = ai.getProto().getTechnology();
				PolyBase[] polys = tech.getShapeOfArc(ai);
				for (int i = 0; i < polys.length; i++)
				{
					PolyBase poly = polys[i];
					if (addLayer(poly, transToTop, null, false, linesInNonMahnattan, true)) hasNonmanhattan = true;
				}
			}
		}

		// now add cell contents
		int cellCount = 0;
		for (Iterator<Geometric> it = cell.searchIterator(bounds); it.hasNext();)
		{
			Geometric geom = it.next();
			if (geom instanceof NodeInst)
			{
				NodeInst ni = (NodeInst)geom;
				if (ni.isCellInstance())
				{
					if (topLevel)
					{
						cellCount++;
						setProgressValue(cellCount, numCells+1);
						setProgressNote("Find blockages in " + ni.describe(false) + "...");
					}
					Rectangle2D subBounds = new Rectangle2D.Double(bounds.getMinX(), bounds.getMinY(),
						bounds.getWidth(), bounds.getHeight());
					DBMath.transformRect(subBounds, ni.transformIn());
					FixpTransform transBack = ni.transformOut(transToTop);
					addArea((Cell)ni.getProto(), subBounds, transBack, false, nextNetNumber, linesInNonMahnattan);
				}
			}
		}
		return hasNonmanhattan;
	}

	/**
	 * Method to parse the exclusion layer on the Generic:RoutingNode object.
	 * Examples:
	 *    ""     no layers excluded
	 *    "ALL"  all layers excluded
	 *    "1"    Metal 1 excluded
	 *    "3,5"  Metals 3 and 5 excluded
	 *    "2-6"  Metals 2 through 6 excluded
	 * @param layers the string with the exclusion layers
	 * @return a List of Integers with exclusion layers
	 */
	private List<Integer> parseExclusionLayers(NodeInst ni)
	{
		List<Integer> exclusionLayers = new ArrayList<Integer>();
		String layers = "";
		Variable var = ni.getVar(Generic.ROUTING_EXCLUSION);
		if (var != null) layers = var.getPureValue(-1);
		layers.replaceAll(" ", "");
		if (layers.length() == 0) return exclusionLayers;
		if (layers.equalsIgnoreCase("all"))
		{
			for(int i=0; i<getNumMetals(); i++)
				exclusionLayers.add(Integer.valueOf(i));
			return exclusionLayers;
		}

		int pt = 0;
		while (pt < layers.length())
		{
			int layNum = TextUtils.atoi(layers.substring(pt));
			while (pt < layers.length() && Character.isDigit(layers.charAt(pt))) pt++;
			if (pt >= layers.length() || layers.charAt(pt) == ',')
			{
				exclusionLayers.add(Integer.valueOf(layNum-1));
				if (pt < layers.length()) pt++;
				continue;
			}
			if (layers.charAt(pt) != '-' || pt >= layers.length()-1)
			{
				System.out.println("ERROR: Routing exclusion node " + ni.describe(false) +
					" has invalid exclusion description: " + layers);
				break;
			}
			pt++;
			int layEnd = TextUtils.atoi(layers.substring(pt));
			while (pt < layers.length() && Character.isDigit(layers.charAt(pt))) pt++;
			for(int i=layNum; i<=layEnd; i++)
				exclusionLayers.add(Integer.valueOf(i-1));
			if (pt < layers.length())
			{
				if (layers.charAt(pt) != ',')
				{
					System.out.println("ERROR: Routing exclusion node " + ni.describe(false) +
						" has invalid exclusion description: " + layers);
					break;
				}
				pt++;
			}
		}

		return exclusionLayers;
	}

	/**
	 * Method to add geometry to the R-Tree.
	 * @param poly the polygon to add (only rectangles are added, so the bounds is used).
	 * @param trans a transformation matrix to apply to the polygon.
	 * @param netID the global network ID of the geometry.
	 * (converted to non-pseudo and stored). False to ignore pseudo-layers.
	 * @param lockTree true to lock the R-Tree before accessing it (when doing parallel routing).
	 * @return true if the geometry is nonmanhattan (and may cause problems).
	 */
	private boolean addLayer(PolyBase poly, FixpTransform trans, MutableInteger netID,
			boolean lockTree, List<EPoint> linesInNonMahnattan, boolean merge)
	{
		boolean isNonmanhattan = false;
		Layer layer = poly.getLayer();
		Layer.Function fun = layer.getFunction();
		if (fun.isMetal())
		{
			// ignore polygons that aren't solid filled areas				TODO: debug
			if (poly.getStyle() != Poly.Type.FILLED) return false;
			poly.transform(trans);
			Rectangle2D bounds = poly.getBox();
			if (bounds == null)
			{
				addPolygon(poly, layer, netID, lockTree);
				for (Point p : poly.getPoints())
					linesInNonMahnattan.add(EPoint.fromLambda(p.getX(), p.getY()));
				isNonmanhattan = true;
			} else
			{
				addRectangle(bounds, layer, netID, lockTree, merge);
			}
		} else if (fun.isContact())
		{
			Rectangle2D bounds = poly.getBounds2D();
			DBMath.transformRect(bounds, trans);
			addVia(EPoint.fromLambda(bounds.getCenterX(), bounds.getCenterY()), layer, netID, lockTree);
		}
		return isNonmanhattan;
	}

	/**
	 * Method to add a rectangle to the metal R-Tree.
	 * @param bounds the rectangle to add.
	 * @param layer the metal layer on which to add the rectangle.
	 * @param netID the global network ID of the geometry.
	 * @param lockTree true to lock the R-Tree before accessing it (when doing parallel routing).
	 */
	private SOGBound addRectangle(Rectangle2D bounds, Layer layer, MutableInteger netID, boolean lockTree, boolean merge)
	{
		SOGBound sogb = null;
		BlockageTree bTree = rTrees.getMetalTree(layer);

		// avoid duplication
		if (merge)
		{
			List<SOGBound> removeThese = null;
			for (Iterator<SOGBound> sea = bTree.search(bounds); sea.hasNext(); )
			{
				SOGBound sBound = sea.next();

				// if an existing bound is bigger than new one, ignore this
				if (sBound.bound.getMinX() <= bounds.getMinX() &&
					sBound.bound.getMaxX() >= bounds.getMaxX() &&
					sBound.bound.getMinY() <= bounds.getMinY() &&
					sBound.bound.getMaxY() >= bounds.getMaxY()) return null;

				// if new one is bigger than an existing bound, remove existing one
				if (bounds.getMinX() <= sBound.bound.getMinX() &&
					bounds.getMaxX() >= sBound.bound.getMaxX() &&
					bounds.getMinY() <= sBound.bound.getMinY() &&
					bounds.getMaxY() >= sBound.bound.getMaxY())
				{
					if (removeThese == null) removeThese = new ArrayList<SOGBound>();
					removeThese.add(sBound);
				}
			}

			if (removeThese != null)
			{
				if (lockTree) bTree.lock();
				try {
					RTNode<SOGBound> rootFixp = bTree.getRoot();
					for(SOGBound s : removeThese)
					{
						RTNode<SOGBound> newRootFixp = RTNode.unLinkGeom(null, rootFixp, s);
						if (newRootFixp != rootFixp) bTree.setRoot(rootFixp = newRootFixp);
					}
				} finally {
					if (lockTree) bTree.unlock();
				}
			}
		}
		
		if (lockTree) bTree.lock();
		try {
			sogb = new SOGBound(ERectangle.fromLambda(bounds), netID);
			RTNode<SOGBound> rootFixp = bTree.getRoot();
			if (rootFixp == null)
			{
				rootFixp = RTNode.makeTopLevel();
				bTree.setRoot(rootFixp);
			}
			RTNode<SOGBound> newRootFixp = RTNode.linkGeom(null, rootFixp, sogb);
			if (newRootFixp != rootFixp) bTree.setRoot(newRootFixp);
		} finally {
			if (lockTree) bTree.unlock();
		}
		return sogb;
	}

	/**
	 * Method to add a polygon to the metal R-Tree.
	 * @param poly the polygon to add.
	 * @param layer the metal layer on which to add the rectangle.
	 * @param netID the global network ID of the geometry.
	 * @param lockTree true to lock the R-Tree before accessing it (when doing parallel routing).
	 */
	private void addPolygon(PolyBase poly, Layer layer, MutableInteger netID, boolean lockTree)
	{
		BlockageTree bTree = rTrees.getMetalTree(layer);
		if (lockTree) bTree.lock();
		try {
			SOGBound sogb = new SOGPoly(ERectangle.fromLambda(poly.getBounds2D()), netID, poly);
			RTNode<SOGBound> rootFixp = bTree.getRoot();
			if (rootFixp == null)
			{
				rootFixp = RTNode.makeTopLevel();
				bTree.setRoot(rootFixp);
			}
			RTNode<SOGBound> newRootFixp = RTNode.linkGeom(null, rootFixp, sogb);
			if (newRootFixp != rootFixp) bTree.setRoot(newRootFixp);
		} finally {
			if (lockTree) bTree.unlock();
		}
	}

	/**
	 * Method to add a point to the via R-Tree.
	 * @param loc the point to add.
	 * @param layer the via layer on which to add the point.
	 * @param netID the global network ID of the geometry.
	 * @param lockTree true to lock the R-Tree before accessing it (when doing parallel routing).
	 */
	private void addVia(EPoint loc, Layer layer, MutableInteger netID, boolean lockTree)
	{
		BlockageTree bTree = rTrees.getViaTree(layer);
		if (lockTree) bTree.lock();
		try {
			SOGBound sogb = new SOGVia(loc, netID);
			RTNode<SOGBound> rootFixp = bTree.getRoot();
			if (rootFixp == null)
			{
				rootFixp = RTNode.makeTopLevel();
				bTree.setRoot(rootFixp);
			}
			RTNode<SOGBound> newRootFixp = RTNode.linkGeom(null, rootFixp, sogb);
			if (newRootFixp != rootFixp) bTree.setRoot(newRootFixp);
		} finally {
			if (lockTree) bTree.unlock();
		}
	}

	private static class BlockageTree {
		private final ReentrantLock lock = new ReentrantLock();
		private RTNode<SOGBound> root;

		public static BlockageTree emptyTree = new BlockageTree(null);

		private BlockageTree(RTNode<SOGBound> root) {
			this.root = root;
		}

		private void lock() { lock.lock(); }

		private void unlock() { lock.unlock(); }

		private RTNode<SOGBound> getRoot() { return root; }

		private void setRoot(RTNode<SOGBound> root) { this.root = root; }

		private boolean isEmpty() { return root == null; }

		private Iterator<SOGBound> search(Rectangle2D searchArea) {
			if (root == null) {
				return Collections.<SOGBound>emptyList().iterator();
			}
			Iterator<SOGBound> it = new RTNode.Search<SOGBound>(searchArea, root, true);
			return it;
		}
	}

	private static class BlockageTrees
	{
		private final BlockageTree[] metalTrees;
		private final BlockageTree[] viaTrees;

		BlockageTrees(int numMetals)
		{
			metalTrees = new BlockageTree[numMetals];
			viaTrees = new BlockageTree[numMetals];
			for (int i = 0; i < metalTrees.length; i++) {
				metalTrees[i] = new BlockageTree(null);
				viaTrees[i] = new BlockageTree(null);
			}
		}

		private BlockageTree getMetalTree(Layer lay) {
			if (lay == null) return BlockageTree.emptyTree;
			return metalTrees[lay.getFunction().getLevel() - 1];
		}

		private BlockageTree getViaTree(Layer lay) {
			if (lay == null) return BlockageTree.emptyTree;
			return viaTrees[lay.getFunction().getLevel() - 1];
		}
	}

	public static class SOGNetID
	{
		private MutableInteger netID;

		SOGNetID(MutableInteger netID)
		{
			this.netID = netID;
		}

		/**
		 * Method to return the global network ID for this SOGNetID.
		 * Numbers > 0 are normal network IDs.
		 * Numbers <= 0 are blockages added around the ends of routes.
		 * @return the global network ID for this SOGNetID.
		 */
		public MutableInteger getNetID() { return netID; }

		/**
		 * Method to set the global network ID for this SOGNetID.
		 * @param n the global network ID for this SOGNetID.
		 */
		public void setNetID(MutableInteger n)
		{
			netID = n;
		}

		public void updateNetID(MutableInteger n, Map<Integer,List<MutableInteger>> netIDsByValue)
		{
			if (isSameBasicNet(n)) return;

			// update all MutableIntegers with the old value
			List<MutableInteger> oldNetIDs = netIDsByValue.get(Integer.valueOf(netID.intValue()));
			if (oldNetIDs == null) return;
			List<MutableInteger> newNetIDs = netIDsByValue.get(Integer.valueOf(n.intValue()));
			for(MutableInteger mi : oldNetIDs)
			{
				mi.setValue(n.intValue());
				newNetIDs.add(mi);
			}
			oldNetIDs.clear();
		}

		/**
		 * Method to tell whether this SOGNetID is on a given network.
		 * Network numbers are encoded integers, where some values indicate
		 * variations on the type of network (for example, the area near routing points
		 * is marked with a "pseudo" blockage that keeps the area clear).
		 * @param otherNetID the network ID of the other net.
		 * @return true if this and the other net IDs are equivalent.
		 */
		public boolean isSameBasicNet(MutableInteger otherNetID)
		{
			int netValue = 0;
			if (netID != null) netValue = netID.intValue();
			if ((netValue & 0xFFFFFFF8) == (otherNetID.intValue() & 0xFFFFFFF8)) return true;
			return false;
		}

		/**
		 * Method to tell whether the network ID on this object is a
		 * pseudo-blockage placed around route endpoints to keep routing traffic away.
		 * @return true if this is pseudo-blockage.
		 */
		public boolean isPseudoBlockage()
		{
			if (netID == null) return false;
			return (netID.intValue() & 1) != 0;
		}

		/**
		 * Method to tell whether the network ID on this object has a specific "route end" bit.
		 * @param bit either 2 or 4, depending on the end of the route being requested.
		 * @return true if this network ID has the specified "route end" bit.
		 */
		public boolean hasEndBit(int bit)
		{
			if (netID == null) return false;
			return (netID.intValue() & bit) != 0;
		}
	}

	/**
	 * Class to define an R-Tree leaf node for geometry in the blockage data structure.
	 */
	public static class SOGBound extends SOGNetID implements RTBounds
	{
		private ERectangle bound;

		SOGBound(ERectangle bound, MutableInteger netID)
		{
			super(netID);
			this.bound = bound;
		}

		@Override
		public ERectangle getBounds() { return bound; }

		@Override
		public String toString() { return "SOGBound on net " + getNetID(); }
	}

	private static class SOGPoly extends SOGBound
	{
		private PolyBase poly;

		SOGPoly(ERectangle bound, MutableInteger netID, PolyBase poly)
		{
			super(bound, netID);
			this.poly = poly;
		}

		public PolyBase getPoly() { return poly; }
	}

	/**
	 * Class to define an R-Tree leaf node for vias in the blockage data structure.
	 */
	public static class SOGVia extends SOGBound
	{
		private EPoint loc;

		SOGVia(EPoint loc, MutableInteger netID)
		{
			super(ERectangle.fromFixp(loc.getFixpX(), loc.getFixpY(), 0, 0), netID);
			this.loc = loc;
		}

		@Override
		public String toString() { return "SOGVia on net " + getNetID(); }
	}

	/******************************* GLOBAL ROUTER *******************************/

	public GlobalRouter doGlobalRouting(Cell cell, RouteBatch[] routeBatches, RouteBatch[] fakeBatches, double wirePitch)
	{
		// make a graph for Global Routing
		GlobalRouter gr = new GlobalRouter(cell, routeBatches, fakeBatches, wirePitch);

		// Do the routing
		gr.solve();
		return gr;
	}

	public class GlobalRouter
	{
		private int numXBuckets, numYBuckets;
		private GRBucket[] buckets;
		private GREdge[] edges;
		private List<GRNet> nets;

		public List<GRNet> getNets() { return nets; }

		public int getXBuckets() { return numXBuckets; }

		public int getYBuckets() { return numYBuckets; }

		public GlobalRouter(Cell c, RouteBatch[] routeBatches, RouteBatch[] fakeBatches, double wirePitch)
		{
			// determine the number of wires to route
			int total = 0;
			for(RouteBatch rb : routeBatches) total += rb.routesInBatch.size();
			if (fakeBatches != null) for(RouteBatch rb : fakeBatches) total += rb.routesInBatch.size();

			// determine the number of X and Y buckets (areas of the circuit to be routed)
			int size = (int)Math.sqrt(total);
			if (size < 2) size = 2;
			ERectangle bounds = c.getBounds();
			if (bounds.getWidth() > bounds.getHeight())
			{
				numXBuckets = size;
				numYBuckets = (int)Math.round(size * bounds.getHeight() / bounds.getWidth());
				if (numYBuckets < 2) numYBuckets = 2;
			} else
			{
				numXBuckets = (int)Math.round(size * bounds.getWidth() / bounds.getHeight());
				if (numXBuckets < 2) numXBuckets = 2;
				numYBuckets = size;
			}
			double bucketWidth = bounds.getWidth() / numXBuckets;
			double bucketHeight = bounds.getHeight() / numYBuckets;

			// determine the capacity between two buckets
			int capacity = (int)Math.round(bucketWidth / wirePitch);
			if (capacity < 1) capacity = 1;

			// build the buckets
			buckets = new GRBucket[numXBuckets * numYBuckets];
			int t = 0;
			for(int y=0; y<numYBuckets; y++)
			{
				for(int x=0; x<numXBuckets; x++)
				{
					double lX = bounds.getMinX() + x * bucketWidth;
					double hX = lX + bucketWidth;
					double lY = bounds.getMinY() + y * bucketHeight;
					double hY = lY + bucketHeight;
					buckets[t++] = new GRBucket(t, new Rectangle2D.Double(lX, lY, hX-lX, hY-lY));
				}
			}

			// build the connections between buckets
			int numEdges = numXBuckets * (numYBuckets-1) + numYBuckets * (numXBuckets-1);
			edges = new GREdge[numEdges];
			int e = 0;
			for(int x=0; x<numXBuckets; x++)
			{
				for(int y=1; y<numYBuckets; y++)
				{
					int sid = y*numXBuckets + x;
					int eid = (y-1)*numXBuckets + x;
					edges[e++] = new GREdge(buckets[sid], buckets[eid], capacity);
				}
			}
			for(int x=1; x<numXBuckets; x++)
			{
				for(int y=0; y<numYBuckets; y++)
				{
					int sid = y*numXBuckets + x;
					int eid = y*numXBuckets + (x-1);
					edges[e++] = new GREdge(buckets[sid], buckets[eid], capacity);
				}
			}

			// build the networks to be routed
			nets = new ArrayList<GRNet>();
			addBatches(routeBatches, bounds, bucketWidth, bucketHeight);
			if (fakeBatches != null) addBatches(fakeBatches, bounds, bucketWidth, bucketHeight);
		}

		private void addBatches(RouteBatch[] batches, ERectangle bounds, double bucketWidth, double bucketHeight)
		{
			for(RouteBatch rb : batches)
			{
				GRNet nn = null;
				for(NeededRoute nr : rb.routesInBatch)
				{
					int x1 = (int)((nr.aX - bounds.getMinX()) / bucketWidth);
					int y1 = (int)((nr.aY - bounds.getMinY()) / bucketHeight);
					int x2 = (int)((nr.bX - bounds.getMinX()) / bucketWidth);
					int y2 = (int)((nr.bY - bounds.getMinY()) / bucketHeight);
					int bucket1 = y1*numXBuckets + x1;
					int bucket2 = y2*numXBuckets + x2;
					if (bucket1 == bucket2) continue;

					if (nn == null)
					{
						nn = new GRNet();
						nets.add(nn);
					}
					GRWire w = new GRWire(nr, buckets[bucket1], buckets[bucket2], EPoint.fromLambda(nr.aX, nr.aY), EPoint.fromLambda(nr.bX, nr.bY));
					nn.addWire(w);
				}
			}
		}

		public void solve()
		{
			// do the routing
			ElapseTimer theTimer = ElapseTimer.createInstance().start();
			route();
			theTimer.end();
			info("Global routing: initialized (took " + theTimer + ")");

			// rip-up and reroute
			int iterations = 4;
			for (int iteration = 0; iteration < iterations; iteration++)
			{
				theTimer.start();
				ripupReroute();
				theTimer.end();
				info("Global routing: Rip-up and reroute pass " + iteration + " (took " + theTimer + ")");
			}

			// set the buckets on each NeededRoute
			for(GRNet net : nets)
			{
				for(GRWire wire : net.wires)
					wire.setPathOnRoute();
			}
		}

		private void route()
		{
			for (GRNet net : nets)
			{
				for (GRWire w : net.getWires())
				{
					if (w.setShortestPath())
						error("ERROR: No path from "+w.n1+" to "+w.n2);
					w.addPath(1);
				}
			}
		}

		private void ripupReroute()
		{
			for (GRNet net : nets)
			{
				for (GRWire w : net.getWires())
				{
					// remove the old routing
					w.addPath(-1);

					// add new path
					if (w.setShortestPath())
						error("ERROR: No path from "+w.n1+" to "+w.n2);
					w.addPath(1);
				}
			}
		}
	};

	public static class GRBucket implements Comparable<GRBucket>
	{
		/** unique bucket number */				private int id;
		/** location of this bucket */			private Rectangle2D bounds;
		/** edges to adjoining buckets */		private List<GREdge> edges;
		/** cost of a path to this bucket */	private double cost;
		/** edge to previous bucket in path */	private GREdge prev;

		public GRBucket(int id, Rectangle2D bounds)
		{
			this.id = id;
			this.bounds = bounds;
			this.edges = new ArrayList<GREdge>();
		}

		public Rectangle2D getBounds() { return bounds; }

		public double getCost() { return cost; }

		public void setCost(double c) { cost = c; }

		public GREdge getPrevEdge() { return prev; }

		public void setPrevEdge(GREdge e) { prev = e; }

		public void addEdge(GREdge e) { edges.add(e); }

		public List<GREdge> getEdges() { return edges; }

		public String toString() { return "BUCKET-" + id; }

		public int compareTo(GRBucket other) { return id - other.id; }
	};

	public static class GRNet
	{
		private List<GRWire> wires;

		GRNet()
		{
			wires = new ArrayList<GRWire>();
		}

		public void addWire(GRWire w) { wires.add(w); }

		public List<GRWire> getWires() { return wires; }
	};

	public static class GRWire
	{
		private GRBucket n1, n2;
		private EPoint pt1, pt2;
		private List<GRPathElement> path;
		private NeededRoute nr;

		public GRWire(NeededRoute nr, GRBucket n1, GRBucket n2, EPoint pt1, EPoint pt2)
		{
			this.nr = nr;
			this.n1 = n1;
			this.n2 = n2;
			this.pt1 = pt1;
			this.pt2 = pt2;
		}

		public void setPathOnRoute()
		{
			nr.buckets = new Rectangle2D[path.size()];
			for(int i=0; i<path.size(); i++) nr.buckets[i] = path.get(i).getBucket().bounds;
		}

		public int getNumPathElements() { return path.size(); }

		public GRBucket getPathBucket(int index) { return path.get(index).getBucket(); }

		public GRBucket getBucket1() { return n1; }

		public GRBucket getBucket2() { return n2; }

		public EPoint getPoint1() { return pt1; }

		public EPoint getPoint2() { return pt2; }

		public NeededRoute getNeededRoute() { return nr; }

		private void addPath(int width)
		{
			for (GRPathElement pe : path)
			{
				if (pe.getEdge() != null)
					pe.getEdge().changeCurrentValue(width);
			}
		}

		/**
		 * Method to find a path between two points.
		 * @return true on error.
		 */
		private boolean setShortestPath()
		{
			GRBucket start = n1, finish = n2;
			path = new ArrayList<GRPathElement>();

			if (start == finish) return false;

			Set<GRWavefrontPoint> h = new TreeSet<GRWavefrontPoint>();
			List<GRBucket> clean = new ArrayList<GRBucket>();

			GRBucket current = start;
			h.add(new GRWavefrontPoint(current, 0));

			for(;;)
			{
				// find the next element in the expanding wavefront
				Iterator<GRWavefrontPoint> it = h.iterator();
				if (it.hasNext())
				{
					GRWavefrontPoint he = it.next();
					current = he.getBucket();
					h.remove(he);
					if (current.getCost() != he.getCost()) continue;
				} else current = null;

				// this is the lowest-cost point on the wavefront, extend to the next bucket
				if (current == null || current == finish) break;

				for (GREdge e : current.getEdges())
				{
					GRBucket next = e.getOtherOne(current);
					if (next == start) continue;

					// figure the cost of going to the next bucket
					double cost = current.getCost() + e.usageCost();
					if (next.getPrevEdge() == null || next.getCost() > cost)
					{
						next.setCost(cost);
						h.add(new GRWavefrontPoint(next, cost));

						if (next.getPrevEdge() == null)
							clean.add(next);
						next.setPrevEdge(e);
					}
				}
			}

			// if we got the finishing bucket, backtrack and build the path
			if (current == finish)
			{
				while (current != null)
				{
					GRPathElement pe = new GRPathElement(current, current.getPrevEdge());
					path.add(pe);
					current = (pe.getEdge() == null) ? null : pe.getEdge().getOtherOne(current);
				}
				Collections.reverse(path);
			} else
			{
				return true;
			}

			// clean all the pointers
			for (GRBucket n : clean)
			{
				n.setPrevEdge(null);
				n.setCost(0);
			}
			return false;
		}
	};

	private static class GREdge
	{
		private GRBucket n1, n2;
		private int current;
		private final int capacity;
		private final double minCost, maxCost;

		public GREdge(GRBucket n1, GRBucket n2, int cap)
		{
			this.n1 = n1;
			this.n2 = n2;
			current = 0;
			minCost = 1;
			maxCost = 16;
			capacity = cap;
			n1.addEdge(this);
			n2.addEdge(this);
		}

		public void changeCurrentValue(int delta) { current += delta; }

		public GRBucket getOtherOne(GRBucket thisOne) { return thisOne == n1 ? n2 : n1; }

		double usageCost()
		{
			if (current <= 0) return minCost;
			if (current >= capacity) return maxCost;
			double ratio = current / (double)capacity;
			return minCost + (maxCost - minCost) * ratio;
		}
	};

	private static class GRPathElement
	{
		private GRBucket n;
		private GREdge e;

		public GRPathElement(GRBucket n, GREdge e)
		{
			this.n = n;
			this.e = e;
		}

		public GRBucket getBucket() { return n; }

		public GREdge getEdge() { return e; }
	};

	private static class GRWavefrontPoint implements Comparable<GRWavefrontPoint>
	{
		private GRBucket n;
		private double cost;

		public GRWavefrontPoint(GRBucket n, double c)
		{
			this.n = n;
			cost = c;
		}

		public GRBucket getBucket() { return n; }

		public double getCost() { return cost; }

		public int compareTo(GRWavefrontPoint other)
		{
			if (cost < other.cost) return -1000000000;
			if (cost > other.cost) return 1000000000;
			return n.compareTo(other.n);
		}
	};

}
