diff --git a/assign/GNUmakefile.in b/assign/GNUmakefile.in
index 62b993671e11609e59bdd2891744a70676a55a19..9bda319379dec101bdec8b1ec76937f26c430dea 100644
--- a/assign/GNUmakefile.in
+++ b/assign/GNUmakefile.in
@@ -21,7 +21,7 @@ all: assign
 include $(TESTBED_SRCDIR)/GNUmakerules
 
 OBJS=parse_top.o parse_ptop.o assign.o pclass.o vclass.o config.o score.o \
-     parser.o solution.o anneal.o
+     parser.o solution.o anneal.o featuredesire.o
 LIBS+= -lm
 LDFLAGS+= -pipe -O3
 CXXFLAGS = -pipe -I/usr/local/include -ftemplate-depth-30
diff --git a/assign/anneal.cc b/assign/anneal.cc
index 6e4773a577dadff887a3e0a61ef1e1a2427b1847..813a8a1d8408945b4535daf96274dc888205f005 100644
--- a/assign/anneal.cc
+++ b/assign/anneal.cc
@@ -123,6 +123,8 @@ inline bool pnode_is_match(tb_vnode *vn, tb_pnode *pn) {
     }
   }
 
+  // Commented out for now because it's too slow!
+#if 0
   // Check for 'local' desires - the reason we take the time to do this here is
   // that they are actually, in many ways, like types with vn->typecount > 1.
   if (matched && !vn->desires.empty()) {
@@ -130,12 +132,7 @@ inline bool pnode_is_match(tb_vnode *vn, tb_pnode *pn) {
     for (desire_it = vn->desires.begin();
 	desire_it != vn->desires.end();
 	desire_it++) {
-      if (desire_it->first[0] != '?') {
-	continue;
-      }
-      if (desire_it->first[1] != '+') {
-	continue;
-      }
+      if (desire_it->is_l_additive() {
       tb_pnode::features_map::iterator feature_it =
 	pn->features.find(desire_it->first);
       if (feature_it == pn->features.end()) {
@@ -159,6 +156,7 @@ inline bool pnode_is_match(tb_vnode *vn, tb_pnode *pn) {
       }
     }
   }
+#endif
 
   return matched;
 }
diff --git a/assign/assign.cc b/assign/assign.cc
index cbf5b0d4a0bb20c20c7322e7f84716b1835a78a3..392cb398b7a06aebea768be0358bd0e857f2f533 100644
--- a/assign/assign.cc
+++ b/assign/assign.cc
@@ -27,6 +27,7 @@
 #include <signal.h>
 #include <sys/signal.h>
 #include <queue>
+#include <algorithm>
 
 using namespace boost;
 
@@ -429,7 +430,7 @@ int mapping_precheck() {
 	int matched_bw = 0;
 	// Keep track of desires had how many 'hits', so that we can tell
 	// if any simply were not matched
-	tb_vnode::desires_count_map matched_desires;
+	map<crope,int> matched_desires;
 
 	// Keep track of which link types had how many 'hits', so that we can
 	// tell which type(s) caused this node to fail
@@ -493,52 +494,41 @@ int mapping_precheck() {
 		    }
 		}
 
-		//
-		// Check features and desires
-		//
-
-		// Desires first
-		for (tb_vnode::desires_map::iterator desire_it = v->desires.begin();
-			desire_it != v->desires.end();
-			desire_it++) {
-		    crope name = (*desire_it).first;
-		    float value = (*desire_it).second;
-		    // Only check for desires that would result in a violation if
-		    // unsatisfied
-		    if (value >= FD_VIOLATION_WEIGHT) {
-			if (matched_desires.find(name) == matched_desires.end()) {
-			    matched_desires[name] = 0;
-			}
-			tb_pnode::features_map::iterator feature_it =
-			    pnode->features.find(name);
-			if (feature_it != pnode->features.end()) {
-			    matched_desires[name]++;
-			} else {
-			    potential_match = false;
-			}
-		    }
-		}
+		/*
+		 * Check features and desires
+		*/
 
-		// Next, features
-		for (tb_pnode::features_map::iterator feature_it = pnode->features.begin();
-			feature_it != pnode->features.end(); feature_it++) {
-		    crope name = (*feature_it).first;
-		    float value = (*feature_it).second;
+		tb_featuredesire_set_iterator
+		    fdit(v->desires.begin(),v->desires.end(),
+			 pnode->features.begin(),pnode->features.end());
+		for (;!fdit.done();fdit++) {
 		    // Skip 'local' and 'global' features
-		    if (name[0] == '*' || name[0] == '?') {
+		    if (fdit->is_global() || fdit->is_local()) {
 			continue;
 		    }
-		    // Only check for feature that would result in a violation if
-		    // undesired
-		    if (value >= FD_VIOLATION_WEIGHT) {
-			tb_vnode::desires_map::iterator desire_it =
-			    v->desires.find(name);
-			if (desire_it == v->desires.end()) {
+		    // Only check for FDs that would result in a violation if
+		    // unmatched.
+		    if (fdit.either_violateable()) {
+			// We look for violateable desires on vnodes so that we
+			// can report them to the user
+			if (fdit.membership() ==
+				tb_featuredesire_set_iterator::BOTH &&
+				fdit.membership() ==
+				tb_featuredesire_set_iterator::FIRST_ONLY &&
+				fdit.first_iterator()->is_violateable() &&
+				matched_desires.find(fdit->name())
+				== matched_desires.end()) {
+			    matched_desires[fdit->name()] = 0;
+			}
+			if (fdit.membership() ==
+				tb_featuredesire_set_iterator::BOTH) {
+			    matched_desires[fdit->name()]++;
+			} else {
 			    potential_match = false;
 			}
 		    }
 		}
-
+		
 		// Check link types
 		tb_vnode::link_counts_map::iterator vit;
 		for (vit = v->link_counts.begin(); vit != v->link_counts.end();
@@ -604,7 +594,7 @@ int mapping_precheck() {
 		cout << "      Too much bandwidth on emulated links!" << endl;
 	    }
 
-	    for (tb_vnode::desires_count_map::iterator dit = matched_desires.begin();
+	    for (map<crope,int>::iterator dit = matched_desires.begin();
 		    dit != matched_desires.end();
 		    dit++) {
 		if (dit->second == 0) {
diff --git a/assign/common.h b/assign/common.h
index 58bc13cd0c2884d0ad06c3e1f1727516dd617614..47d284fcc6d06692e49c2e3d782f9a7e1d6f6336 100644
--- a/assign/common.h
+++ b/assign/common.h
@@ -21,6 +21,8 @@ using namespace __gnu_cxx;
 #endif
 
 #include "config.h"
+#include <utility>
+#include <rope>
 
 #include <boost/graph/adjacency_list.hpp>
 
@@ -219,4 +221,7 @@ template <class T> struct hashptr {
 #define HASH_MAP
 #endif
 
+// For use in functions that want to return a score/violations pair
+typedef pair<double,int> score_and_violations;
+
 #endif
diff --git a/assign/featuredesire.cc b/assign/featuredesire.cc
new file mode 100644
index 0000000000000000000000000000000000000000..fbb026d59e2b21e2cc367e954eb74fd9cdcd5a6f
--- /dev/null
+++ b/assign/featuredesire.cc
@@ -0,0 +1,308 @@
+/*
+ * EMULAB-COPYRIGHT
+ * Copyright (c) 2004 University of Utah and the Flux Group.
+ * All rights reserved.
+ */
+
+/*
+ * featuredesire.cc - implementation of the objects from featuredesire.h
+ */
+
+#include "featuredesire.h"
+
+/*********************************************************************
+ * tb_featuredesire
+ *********************************************************************/
+tb_featuredesire::name_featuredesire_map
+    tb_featuredesire::featuredesires_by_name;
+
+
+/*
+ * Constructor
+ */
+tb_featuredesire::tb_featuredesire(crope _my_name) : my_name(_my_name),
+				    global(false), local(false),
+				    l_additive(false), g_one_is_okay(false),
+				    g_more_than_one(false),
+				    in_use_globally(0) { 
+    static int highest_id = 0;
+		
+    // Pick a unique numeric identifier for this feature/desire
+    id = highest_id++;
+
+    // Find out which flags should be set for this feature/desire from the name
+    switch (my_name[0]) {
+	case '?':
+	    // A local feature - the second character tell us what kind.
+	    // Currently only additive are supported
+	    local = true;
+	    switch(my_name[1]) {
+		case '+':
+		    l_additive = true;
+		    break;
+		default:
+		    cerr << "*** Invalid local feature type in feature " <<
+			my_name << endl;
+		    exit(EXIT_FATAL);
+	    }
+	    break;
+	case '*':
+	    // A global feature - the second character tells us what kind.
+	    global = true;
+	    switch(my_name[1]) {
+		case '&':
+		    g_one_is_okay = true;
+		    break;
+		case '!':
+		    g_more_than_one = true;
+		    break;
+		default:
+		    cerr << "*** Invalid global feature type in feature " <<
+			my_name << endl;
+		    exit(EXIT_FATAL);
+	    }
+	    break;
+	default:
+	    // Just a regular feature
+	    break;
+    }
+
+    // Place this into the map for finding featuredesire objects by
+    // name
+    assert(featuredesires_by_name.find(my_name)
+	    == featuredesires_by_name.end());
+    featuredesires_by_name[my_name] = this;
+}
+
+/*
+ * Operators
+ */
+ostream &operator<<(ostream &o, const tb_featuredesire &fd) {
+    // Perhaps this should print more information like the flags and/or global
+    // use count
+    o << fd.my_name;
+}
+
+
+/*
+ * Static functions
+ */
+tb_featuredesire *tb_featuredesire::get_featuredesire_obj(const crope name) {
+    name_featuredesire_map::iterator it =
+	featuredesires_by_name.find(name);
+    if (it == featuredesires_by_name.end()) {
+	return new tb_featuredesire(name);
+    } else {
+	return it->second;
+    }
+}
+
+/*
+ * Functions for maintaining state for global FDs
+ */
+void tb_featuredesire::add_global_user(int howmany) {
+    assert(global);
+    in_use_globally += howmany;
+}	
+
+void tb_featuredesire::remove_global_user(int howmany) {
+    assert(global);
+    in_use_globally -= howmany;
+    assert(in_use_globally >= 0);
+}
+
+/*********************************************************************
+ * tb_node_featuredesire
+ *********************************************************************/
+
+/*
+ * Constructor
+ */
+tb_node_featuredesire::tb_node_featuredesire(crope _name, double _weight) :
+	weight(_weight), violateable(false), used_local_capacity(0.0f) {
+    // We'll want to change in the in the future to seperate out the notions of
+    // score and violations
+    if (weight >= FD_VIOLATION_WEIGHT) {
+	violateable = true;
+    }
+    featuredesire_obj = tb_featuredesire::get_featuredesire_obj(_name);
+    assert(featuredesire_obj != NULL);
+}
+
+/*
+ * Add a global user of this feature, and return the score and violations
+ * induced by this
+ */
+score_and_violations tb_node_featuredesire::add_global_user() const {
+    featuredesire_obj->add_global_user();
+
+    double score = 0.0f;
+    int violations = 0;
+
+    // Handle the scoring and violations from this change
+    if (featuredesire_obj->is_g_one() &&
+	    (featuredesire_obj->global_use_count() >= 2)) {
+	// Have to penalize this one, it's not the first
+	score += weight;
+	if (violateable) {
+	    violations += 1;
+	}
+    } else if (featuredesire_obj->is_g_more() &&
+	    (featuredesire_obj->global_use_count() == 1)) {
+	// Only penalize the first one
+	score += weight;
+	if (violateable) {
+	    violations += 1;
+	}
+    }
+    return score_and_violations(score,violations);
+}
+
+
+/*
+ * Remove a global user of this feature, and return the score and violations
+ * induced by this
+ */
+score_and_violations tb_node_featuredesire::remove_global_user() const {
+    featuredesire_obj->remove_global_user();
+
+    double score = 0.0f;
+    int violations = 0;
+
+    // Handle the scoring and violations from this change
+    if (featuredesire_obj->is_g_one() &&
+	    (featuredesire_obj->global_use_count() >= 1)) {
+	// We're not removing the final one, so we count it
+	score += weight;
+	if (violateable) {
+	    violations += 1;
+	}
+    } else if (featuredesire_obj->is_g_more() &&
+	    (featuredesire_obj->global_use_count() == 0)) {
+	// Only count it if we just removed the final one
+	score += weight;
+	if (violateable) {
+	    violations += 1;
+	}
+    }
+    return score_and_violations(score,violations);
+}
+
+/*
+ * Add a local user of a feature
+ * XXX - Should we add more violations if we hit multiples of the capacity?
+ * XXX - These are always assumed to be violatable
+ */
+score_and_violations tb_node_featuredesire::add_local(double amount) {
+    double oldvalue = used_local_capacity;
+    used_local_capacity += amount;
+    if ((oldvalue <= weight) && (used_local_capacity > weight)) {
+	// This one pushed us over the edge, violation!
+	return score_and_violations(SCORE_OVERUSED_LOCAL_FEATURE,1);
+    } else {
+	// We're good, doesn't cost anything;
+	return score_and_violations(0.0f,0);
+    }
+}
+
+/*
+ * Subtract a local user of a feature
+ */
+score_and_violations tb_node_featuredesire::subtract_local(double amount) {
+    double oldvalue = used_local_capacity;
+    used_local_capacity -= amount;
+    if ((oldvalue > weight) && (used_local_capacity <= weight)) {
+	// Back down to below capacity, remove a violation
+	return score_and_violations(SCORE_OVERUSED_LOCAL_FEATURE,1);
+    } else {
+	// We're good, doesn't cost anything;
+	return score_and_violations(0.0f,0);
+    }
+
+}
+
+/*********************************************************************
+ * tb_featuredesire_set_iterator
+ *********************************************************************/
+
+tb_featuredesire_set_iterator::tb_featuredesire_set_iterator(
+	node_fd_set::iterator _begin1, node_fd_set::iterator _end1,
+	node_fd_set::iterator _begin2, node_fd_set::iterator _end2) :
+	    it1(_begin1), end1(_end1), it2(_begin2), end2(_end2) {
+    /*
+     * Figure out what the next element of the set is
+     */
+    // First check to see if we've hit the end of both lists
+    if ((it1 == end1) && (it2 == end2)) {
+	current = end1;
+    } else if ((it1 != end1) && ((it2 == end2) || (*it1 < *it2))) {
+	// If one has hit the end of the list, go with the other - otherwise,
+	// go with the smaller of the two.
+	current_membership = FIRST_ONLY;
+	current = it1;
+    } else if ((it1 == end1) || (*it2 < *it1)) {
+	current_membership = SECOND_ONLY;
+	current = it2;
+    } else {
+	// If neither is smaller, they must be equal
+	current = it1;
+	current_membership = BOTH;
+    }
+}
+
+bool tb_featuredesire_set_iterator::done() const {
+    return((it1 == end1) && (it2 == end2));
+}
+
+void tb_featuredesire_set_iterator::operator++(int) {
+    /*
+     * Advance the iterator(s)
+     */
+    // Make sure they don't try to go off the end of the list
+    assert(!done());
+    // If one iterator has gone off the end of its list, advance the other one
+    // - otherwise, go with the smaller one. Or, if they are equal,  increment
+    // both.
+    if ((it1 != end1) && ((it2 == end2) ||  (*it1 < *it2))) {
+	it1++;
+    } else if ((it1 == end1) || (*it2 < *it1)) {
+	it2++;
+    } else {
+	// If neither was smaller, they must be equal - advance both
+	it1++;
+	it2++;
+    }
+
+    /*
+     * Figure out what the next element of the set is
+     */
+    // First check to see if we've hit the end of both lists
+    if ((it1 == end1) && (it2 == end2)) {
+	current = end1;
+    } else if ((it2 == end2) || ((it1 != end1) && (*it1 < *it2))) {
+	// If one has hit the end of the list, go with the other - otherwise,
+	// go with the smaller of the two.
+	current_membership = FIRST_ONLY;
+	current = it1;
+    } else if ((it1 == end1) || (*it2 < *it1)) {
+	current_membership = SECOND_ONLY;
+	current = it2;
+    } else {
+	// If neither is smaller, they must be equal
+	current = it1;
+	current_membership = BOTH;
+    }
+}
+
+bool tb_featuredesire_set_iterator::both_equiv() const {
+    assert(current_membership == BOTH);
+    return (it1->equivalent(*it2));
+}
+
+bool tb_featuredesire_set_iterator::either_violateable() const {
+    if (current_membership == BOTH) {
+	return (it1->is_violateable() || it2->is_violateable());
+    } else {
+	return current->is_violateable();
+    }
+}
diff --git a/assign/featuredesire.h b/assign/featuredesire.h
new file mode 100644
index 0000000000000000000000000000000000000000..e6006a5c0cddf3a9b09b72f8c92f85fd31a21fd3
--- /dev/null
+++ b/assign/featuredesire.h
@@ -0,0 +1,227 @@
+/*
+ * EMULAB-COPYRIGHT
+ * Copyright (c) 2004 University of Utah and the Flux Group.
+ * All rights reserved.
+ */
+
+#ifndef __FEATUREDESIRE_H
+#define __FEATUREDESIRE_H
+
+#include "common.h"
+
+#include <rope>
+#include <map>
+#include <set>
+#include <slist>
+
+/*
+ * Base class for features and desires - not intended to be used directly, only
+ * to be subclassed by tb_feature and tb_desire
+ */
+class tb_featuredesire {
+    public:
+
+	/*
+	 * Note: Constructor is below - use get_featuredesire_obj instead
+	 */
+
+	~tb_featuredesire() { ; }
+
+	/*
+	 * Get the object for a particular feature/desire - if one does not
+	 * exist, creates a new one. Otherwise, returns the existing object
+	 */
+	static tb_featuredesire *get_featuredesire_obj(const crope name);
+
+	/*
+	 * Silly accessor functions
+	 */
+	inline bool  is_global()        const { return global;          }
+	inline bool  is_local()         const { return local;           }
+	inline bool  is_l_additive()	const { return l_additive;	}
+	inline bool  is_g_one()		const { return g_one_is_okay;	}
+	inline bool  is_g_more()	const { return g_more_than_one; }
+	inline int   global_use_count() const { return in_use_globally; }
+	inline crope name()             const { return my_name;         }
+
+	/*
+	 * Operators, primarily for use with the STL
+	 */
+	inline bool operator==(const tb_featuredesire &o) const {
+	    return (id == o.id);
+	}
+
+	inline bool operator<(const tb_featuredesire &o) const {
+	    return (id < o.id);
+	}
+
+	friend ostream &operator<<(ostream &o, const tb_featuredesire &fd);
+
+	/*
+	 * Functions for maintaining state for global FDs
+	 */
+	void add_global_user(int howmany = 1);
+	void remove_global_user(int howmany = 1);
+
+    private:
+	/*
+	 * This is private, so that we can force callers to go through the
+	 * static get_featuredesire_obj function which uses the existing desire
+	 * if there is one
+	 */
+	explicit tb_featuredesire(crope _my_name);
+
+	// Globally unique identifier
+	int id;
+
+	// String name of this FD, used for debugging purposes only
+	crope my_name;
+
+	// Flags
+	bool global;          // Whether this FD has global scope
+	bool g_one_is_okay;   // If global, we can have one without penalty
+	bool g_more_than_one; // If global, more than one doesn't incur
+			      // additional penalty
+	bool local;           // Whether this FD has local scope
+	bool l_additive;      // If a local FD, is additive
+
+	// Counts how many instances of this feature are in use across all
+	// nodes - for use with global nodes
+	int in_use_globally;
+
+	typedef map<crope,tb_featuredesire*> name_featuredesire_map;
+	static name_featuredesire_map featuredesires_by_name;
+};
+
+/*
+ * This class stores information about a particular feature or desire for a
+ * particular node, rather than global information about the feature or desire.
+ */
+class tb_node_featuredesire {
+    public:
+	tb_node_featuredesire(crope _name, double _weight);
+
+	~tb_node_featuredesire() { ; }
+
+	/*
+	 * Operators, mostly for use with the STL
+	 */
+	const bool operator==(const tb_node_featuredesire &o) const {
+	    // Note: Compares that two FDs are have the same name/ID, but NOT
+	    // that they have the same weight - see equivalent() below for that
+	    return(*featuredesire_obj == *(o.featuredesire_obj));
+	}
+
+	const bool operator<(const tb_node_featuredesire &o) const {
+	    return(*featuredesire_obj < *(o.featuredesire_obj));
+	}
+
+	// Since we have to use == to compare names, for the STL's sake, this
+	// function checks to make sure that the node-specific parts are
+	// equivalent too
+	const bool equivalent(const tb_node_featuredesire &o) const {
+	    return ((*this == o) && (weight == o.weight) &&
+		    (violateable == o.violateable));
+	}
+
+	/*
+	 * Silly accesors
+	 */
+	inline const bool   is_violateable() const { return violateable; }
+	inline const double cost()           const { return weight;      }
+	inline const double used()	     const { return used_local_capacity; }
+
+	/*
+	 * Proxy functions for the stuff in tb_featuredesire
+	 */
+	const crope name()      const { return featuredesire_obj->name();      }
+	const bool  is_local()  const { return featuredesire_obj->is_local();  }
+	const bool  is_global() const { return featuredesire_obj->is_global(); }
+	const bool  is_l_additive() const {
+	    return featuredesire_obj->is_l_additive();
+	}
+
+	score_and_violations add_global_user() const;
+	score_and_violations remove_global_user() const;
+
+	/*
+	 * Functions for tracking local features/desires
+	 */
+	score_and_violations add_local(double amount);
+	score_and_violations subtract_local(double amount);
+
+    protected:
+
+	double weight;
+	bool violateable;
+	tb_featuredesire *featuredesire_obj;
+	double used_local_capacity;
+};
+
+/*
+ * Types to hold virtual nodes' sets of desires and physical nodes' sets of
+ * features
+ */
+typedef slist<tb_node_featuredesire> node_feature_set;
+typedef slist<tb_node_featuredesire> node_desire_set;
+typedef slist<tb_node_featuredesire> node_fd_set;
+
+/*
+ * Kind of like an iterator, but not quite - used for going through a virtual
+ * node's desires and a physical node's features, and deterimining which are
+ * only in one set, and which are in both
+ */
+class tb_featuredesire_set_iterator {
+    public:
+	/*
+	 * Constructors
+	 */
+	tb_featuredesire_set_iterator(node_fd_set::iterator _begin1,
+		node_fd_set::iterator _end1,
+		node_fd_set::iterator _begin2,
+		node_fd_set::iterator _end2);
+
+	// Enum for indicating which set(s) an element belongs to
+	typedef enum { FIRST_ONLY, SECOND_ONLY, BOTH } set_membership;
+
+	// Return whether or not we've iterated to the end of both sets
+	bool done() const;
+
+	// If we have a membership() of BOTH, do they pass the equivalence
+	// test?
+	bool both_equiv() const;
+
+	// Is either of the two elements violateable?
+	bool either_violateable() const;
+
+	// Iterate to the next element of the set
+	void operator++(int);
+
+	// Return the member of the set we're currently iterating to
+	const tb_node_featuredesire &operator*() const {
+	    return *current;
+	}
+	
+	// Return the member of the set we're currently iterating to
+	const tb_node_featuredesire *operator->() const {
+	    return &*current;
+	}
+
+	// Return the set membership of the current element
+	const set_membership membership() const {
+	    return current_membership;
+	}
+
+	// Give out the iterators to the two elements, so that they can be
+	// operated upon
+	node_fd_set::iterator &first_iterator()  { return it1; }
+	node_fd_set::iterator &second_iterator() { return it2; }
+	
+    private:
+	node_fd_set::iterator it1, end1;
+	node_fd_set::iterator it2, end2;
+	node_fd_set::iterator current;
+	set_membership current_membership;
+};
+
+#endif
diff --git a/assign/parse_ptop.cc b/assign/parse_ptop.cc
index fc163baa991c323470e172e327bae7e2e8ef9d50..2508d6557d9892e8184a8a701de2d169b6453c3f 100644
--- a/assign/parse_ptop.cc
+++ b/assign/parse_ptop.cc
@@ -54,12 +54,12 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
 {
   int num_nodes = 0;
   int line=0,errors=0;
-  char inbuf[1024];
+  char inbuf[4096];
   string_vector parsed_line;
 
   while (!i.eof()) {
     line++;
-    i.getline(inbuf,1024);
+    i.getline(inbuf,4096);
     parsed_line = split_line(inbuf,' ');
     if (parsed_line.size() == 0) {continue;}
 
@@ -128,7 +128,9 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
 	    ptop_error("Bad node line, bad cost: " << gcost << ".");
 	    gcost = 0;
 	  }
-	  p->features[feature] = gcost;
+
+	  p->features.push_front(tb_node_featuredesire(feature,gcost));
+
 	}
 	/*
 	 * Parse any other node options or flags
@@ -163,6 +165,7 @@ int parse_ptop(tb_pgraph &PG, tb_sgraph &SG, istream& i)
 		ptop_error("Bad flag given: " << flag << ".");
 	    }
 	}
+	p->features.sort();
 	pname2vertex[name] = pv;
       }
     } else if (command.compare("link") == 0) {
diff --git a/assign/parse_top.cc b/assign/parse_top.cc
index f5200dbd2f0981230b5bb750da1109045f816517..12f230912042d2a0e7087b07cc2fbe08a1bb17c8 100644
--- a/assign/parse_top.cc
+++ b/assign/parse_top.cc
@@ -148,10 +148,12 @@ int parse_top(tb_vgraph &VG, istream& i)
 		      top_error("Bad desire, bad weight.");
 		      gweight = 0;
 		  }
-		  v->desires[desirename] = gweight;
+		  v->desires.push_front(
+			  tb_node_featuredesire(desirename,gweight));
 	      }
 	  }
 	}
+	v->desires.sort();
       }
     } else if (command.compare("link") == 0) {
       if (parsed_line.size() < 8) {
diff --git a/assign/pclass.cc b/assign/pclass.cc
index 2cdc9467f5aaa31f4c4b9dfb9f0fb5bd69e4fb1c..143900b04043d25709e0b660462ff772d4add37c 100644
--- a/assign/pclass.cc
+++ b/assign/pclass.cc
@@ -102,29 +102,22 @@ int pclass_equiv(tb_pgraph &PG, tb_pnode *a,tb_pnode *b)
   }
 
   // check features
-  for (tb_pnode::features_map::iterator it=a->features.begin();
-       it != a->features.end();++it) {
-    const crope &a_feature = (*it).first;
-    const double a_weight = (*it).second;
-    
-    tb_pnode::features_map::iterator bit;
-    bit = b->features.find(a_feature);
-    if ((bit == b->features.end()) || ((*bit).second != a_weight)) 
-      return 0;
-  }
+  tb_featuredesire_set_iterator fdit(a->features.begin(),a->features.end(),
+	  b->features.begin(),b->features.end());
+
+  while (!fdit.done()) {
+      if (fdit.membership() == tb_featuredesire_set_iterator::BOTH) {
+	  // Great, we've got a feature that's in both, just make sure that
+	  // the two are equivalent (score, etc.)
+	  if (!fdit.both_equiv()) {
+	      return 0;
+	  }
+      } else {
+	  // Got a feature that's in one but not the other
+	  return 0;
+      }
 
-  // have to go both ways in case the second node has a feature the first
-  // doesn't
-  for (tb_pnode::features_map::iterator it=b->features.begin();
-       it != b->features.end();++it) {
-    const crope &b_feature = (*it).first;
-    const double b_weight = (*it).second;
-    
-    tb_pnode::features_map::iterator ait;
-    ait = a->features.find(b_feature);
-    if (ait == a->features.end()) {
-      return 0;
-    }
+      fdit++;
   }
 
   // Check links
diff --git a/assign/physical.h b/assign/physical.h
index f8b8b8f0c8f793b5a8970b045291232aa33390fa..e00ccc9591849905be57e74bb7f19f3043d514ad 100644
--- a/assign/physical.h
+++ b/assign/physical.h
@@ -33,6 +33,8 @@ using namespace __gnu_cxx;
 #include <hash_map>
 #endif
 
+#include "featuredesire.h"
+
 // Icky, but I can't include virtual.h here
 class tb_vnode;
 typedef hash_set<tb_vnode*,hashptr<tb_vnode*> > tb_vnode_set;
@@ -127,14 +129,12 @@ public:
 	  }
   };
 
-  typedef hash_map<crope,type_record*> types_map;
-  typedef hash_map<crope,double> features_map;
-
   // contains max nodes for each type
+  typedef hash_map<crope,type_record*> types_map;
   types_map types;
 
   // contains cost of each feature
-  features_map features;
+  node_feature_set features;
 
   crope name;			// name of the node
   bool typed;			// has it been typed
@@ -211,9 +211,9 @@ public:
 	   it!=node.types.end();it++) 
 	o << "    " << (*it).first << " -> " << (*it).second << endl;
       o << "  Features:" << endl;
-      for (features_map::const_iterator it = node.features.begin();
-	   it!=node.features.end();it++) 
-	cout << "    " << (*it).first << " -> " << (*it).second << endl;
+      for (node_feature_set::const_iterator it = node.features.begin();
+	   it != node.features.end(); it++) 
+	cout << "    " << it->name() << " -> " << it->cost() << endl;
       o << "  Current Type: " << node.current_type << endl; /* <<
 	" (" << node.current_load << "/" << node.max_load << ")" <<  endl; */
       o << "  switches=";
diff --git a/assign/score.cc b/assign/score.cc
index 39d30fc1ed92f3d55988922d5cf1a3caefdc4c27..a14a07fd8864cd6d25f97bfdd32757d4c0b6ef9a 100644
--- a/assign/score.cc
+++ b/assign/score.cc
@@ -38,6 +38,7 @@ using namespace boost;
 #include "virtual.h"
 #include "pclass.h"
 #include "score.h"
+#include "featuredesire.h"
 
 #include "math.h"
 
@@ -62,14 +63,8 @@ bool find_best_link(pvertex pv,pvertex switch_pv,tb_vlink *vlink,
 int find_interswitch_path(pvertex src_pv,pvertex dest_pv,
 			  int bandwidth,pedge_path &out_path,
 			  pvertex_list &out_switches);
-inline double fd_score(tb_vnode *vnode,tb_pnode *pnode,int &out_fd_violated,
-	bool include_violations);
-inline void add_global_fds(tb_vnode *vnode,tb_pnode *pnode);
-inline void remove_global_fds(tb_vnode *vnode,tb_pnode *pnode);
-inline double add_stateful_fds(tb_vnode *vnode, tb_pnode *pnode,
-			       int &out_fd_violated);
-inline double remove_stateful_fds(tb_vnode *vnode, tb_pnode *pnode,
-				  int &out_fd_violated);
+score_and_violations score_fds(tb_vnode *vnode, tb_pnode *pnode, bool add);
+
 void score_link_info(vedge ve, tb_pnode *src_pnode, tb_pnode *dst_pnode,
 	tb_vnode *src_vnode, tb_vnode *dst_vnode);
 void unscore_link_info(vedge ve, tb_pnode *src_pnode, tb_pnode *dst_pnode,
@@ -100,12 +95,6 @@ void score_link_endpoints(pedge pe);
 #define MIN(a,b) (((a) < (b))? (a) : (b))
 #define MAX(a,b) (((a) > (b))? (a) : (b))
 
-/*
- * For features and desires that have a some sort of global impact
- */
-typedef hash_map<crope,unsigned int> fd_count_map;
-fd_count_map global_fd_set;
-
 /*
  * score()
  * Returns the score.
@@ -529,14 +518,10 @@ void remove_node(vvertex vv)
   /*
    * Scoring for features and desires
    */
-  int fd_violated;
-  double fds=fd_score(vnode,pnode,fd_violated,false);
-  SSUB(fds);
-  remove_global_fds(vnode,pnode);
-  double sfds=remove_stateful_fds(vnode,pnode,fd_violated);
-  SSUB(sfds);
-  violated -= fd_violated;
-  vinfo.desires -= fd_violated;
+  score_and_violations sv = score_fds(vnode,pnode,false);
+  SSUB(sv.first);
+  violated -= sv.second;
+  vinfo.desires -= sv.second;
 
   SDEBUG(cerr << "  new score = " << score << " new violated = " << violated << endl);
 }
@@ -1152,14 +1137,12 @@ int add_node(vvertex vv,pvertex pv, bool deterministic, bool is_fixed)
   violated--;
 
   // features/desires
-  add_global_fds(vnode,pnode);
-  int fd_violated;
-  double fds = fd_score(vnode,pnode,fd_violated,is_fixed);
-  SADD(fds);
-  double sfds = add_stateful_fds(vnode,pnode,fd_violated);
-  SADD(sfds);
-  violated += fd_violated;
-  vinfo.desires += fd_violated;
+  score_and_violations sv = score_fds(vnode,pnode,true);
+  SADD(sv.first);
+  if (!is_fixed) {
+      violated += sv.second;
+      vinfo.desires += sv.second;
+  }
 
   // pclass
   if ((!disable_pclasses) && (!tr->is_static) && pnode->my_class &&
@@ -1579,249 +1562,96 @@ UNSCORE_TRIVIAL:
   vlink->link_info.type_used = tb_link_info::LINK_UNMAPPED;
 }
 
-double fd_score(tb_vnode *vnode,tb_pnode *pnode,int &fd_violated,
-	bool ignore_violations)
-{
-  double fd_score=0;
-  fd_violated=0;
-
-  double value;
-  tb_vnode::desires_map::iterator desire_it;
-  tb_pnode::features_map::iterator feature_it;
-
-  // Optimize the case where the vnode has no desires
-  if (!vnode->desires.empty()) {
-    for (desire_it = vnode->desires.begin();
-	desire_it != vnode->desires.end();
-	desire_it++) {
-      // We ignore local desires, which are handled by add_stateful_fds() and
-      // remove_stateful_fds()
-      if (desire_it->first[0] == '?') {
-	continue;
-      }
-      feature_it = pnode->features.find((*desire_it).first);
-      SDEBUG(cerr << "  desire = " << (*desire_it).first << " " <<
-	  (*desire_it).second << endl);
-
-      if (feature_it == pnode->features.end()) {
-	// Unmatched desire.  Add cost.
-	SDEBUG(cerr << "    unmatched" << endl);
-	value = (*desire_it).second;
-	fd_score += SCORE_DESIRE*value;
-	if ((value >= FD_VIOLATION_WEIGHT) && (!ignore_violations)) {
-	  fd_violated++;
-	}
-      } else {
-	// Features/desires with a '+' at the front are additive - rather than
-	// 'cancelling out' if both have it, they add together, possibly
-	// resulting in a violation
-	if (((*desire_it).first)[0] == '+') {
-	  value = (*desire_it).second + (*feature_it).second;
-	  SDEBUG(cerr << "    additive - total " << value << endl);
-	  fd_score += SCORE_DESIRE*value;
-	  if ((value >= FD_VIOLATION_WEIGHT) && (!ignore_violations)) {
-	    fd_violated++;
-	  }
-	}
-      }
-    }
-  }
-
-  // Optimize the case where the pnode has no features
-  if (!pnode->features.empty()) {
-    for (feature_it = pnode->features.begin();
-	feature_it != pnode->features.end();++feature_it) {
-      // We ignore local features, which are handled by add_stateful_fds() and
-      // remove_stateful_fds()
-      if (feature_it->first[0] == '?') {
-	continue;
-      }
-      crope feature_name = (*feature_it).first;
-      value = (*feature_it).second;
-      SDEBUG(cerr << "  feature = " << feature_name
-	  << " " << (*feature_it).second << endl);
-
-      if (feature_name[0] == '*') {
-	SDEBUG(cerr << "    global" << endl);
-	// Handle features with global scope - for now, these don't have
-	// desires to go with them, but we may want to change that at some
-	// point
-	  switch (feature_name[1]) {
-	    case '&': // A 'one is okay' feature - only score if we have more
-	              // than one pnode with this feature
-	      SDEBUG(cerr << "    'one is okay'" << endl);
-	      if (global_fd_set[feature_name] > 1) {
-		SDEBUG(cerr << "      but more than one" << endl);
-		fd_score+=SCORE_FEATURE*value;
-		if ((value >= FD_VIOLATION_WEIGHT) && (!ignore_violations)) {
-		  fd_violated++;
+score_and_violations score_fds(tb_vnode *vnode, tb_pnode *pnode, bool add) {
+    // Iterate through the set of features and desires on these nodes
+    tb_featuredesire_set_iterator fdit(vnode->desires.begin(),
+	    vnode->desires.end(), pnode->features.begin(),
+	    pnode->features.end());
+
+    // Keep track of the score and violations changes we rack up as we go along
+    double score_delta = 0.0f;
+    int violations_delta = 0;
+
+    for (;!fdit.done();fdit++) {
+	node_fd_set::iterator fit, dit;
+	dit = fdit.first_iterator();
+	fit = fdit.second_iterator();
+	// What we do here depends on whether it's a feature of the vnode, a desire
+	// of the pnode, or both
+	switch (fdit.membership()) {
+	    case tb_featuredesire_set_iterator::BOTH:
+		/*
+		 * On both 
+		 */
+		// note: Right now, global features cannot be
+		// desires, so there is no code path for them here
+		if (fdit->is_local()) {
+		    if (fdit->is_l_additive()) {
+			score_and_violations delta;
+			if (add) {
+			    delta = fit->add_local(dit->cost());
+			} else {
+			    delta = fit->subtract_local(dit->cost());
+			}
+			score_delta += delta.first;
+			violations_delta += delta.second;
+		    }
+		} else {
+		    // Just a normal feature or desire - since both have it,
+		    // no need to do any scoring
 		}
-	      }
-	      break;
-	    case '!': // A 'more than one is okay' feature - if we already have one,
-	              // a second doesn't incur further penalty
-	      SDEBUG(cerr << "    ' more than one is okay'" << endl);
-	      if (global_fd_set[feature_name] == 1) {
-		SDEBUG(cerr << "      but only one" << endl);
-		fd_score+=SCORE_FEATURE*value;
-		if ((value >= FD_VIOLATION_WEIGHT) && (!ignore_violations)) {
-		  fd_violated++;
+		break;
+	    case tb_featuredesire_set_iterator::FIRST_ONLY:
+		/*
+		 * On the vnode, but not the pnode
+		 */
+		// May be a violation to not have the matching feature
+		if (fdit->is_violateable()) {
+		    violations_delta++;
 		}
-	      }
-	      break;
-	    default:
-	      // Global features are required to have some kind of type
-	      cout << "Bad global feature " << (*feature_it).first << endl;
-	      exit(EXIT_FATAL);
-	  }
-      } else {
-	desire_it = vnode->desires.find(feature_name);
-	if (desire_it == vnode->desires.end()) {
-	  // Unused feature.  Add weight
-	  SDEBUG(cerr << "    unused" << endl);
-	  fd_score+=SCORE_FEATURE*value;
-	  if ((value >= FD_VIOLATION_WEIGHT) && (!ignore_violations)) {
-	    fd_violated++;
-	  }
-	}
-      }
-    }
-  }
-
-  SDEBUG(cerr << "  Total feature score: " << fd_score << endl);
-  return fd_score;
-}
-
-/*
- * Compute the features/desires score resulting from mapping the given vnode to
- * the given vnode. We do this specially for local features and desires,
- * because they are stateful (ie. we have to explicitly add and subtract them,
- * we can't just compute the current value statelessly.)
- * 
- */
-double add_stateful_fds(tb_vnode *vnode, tb_pnode *pnode,
-                        int &out_fd_violated) {
-  tb_vnode::desires_map::iterator desire_it;
-  double score = 0.0f;
-  if (!vnode->desires.empty()) {
-    for (desire_it = vnode->desires.begin();
-	desire_it != pnode->features.end();++desire_it) {
-      if (desire_it->first[0] == '?') {
-	// Found a local desire - does the pnode have it?
-	tb_pnode::features_map::iterator feature_it;
-	feature_it = pnode->features.find(desire_it->first);
-	if (feature_it == pnode->features.end()) {
-	  // Didn't find the feature - violation!
-	  score += SCORE_MISSING_LOCAL_FEATURE;
-	  out_fd_violated++;
-	} else {
-	  // Found the feature, score it
-	  switch (((*desire_it).first)[1]) {
-	    case '+':
-	      /*
-	       * A 'sum' local feature - we add up all of the vnode desires,
-	       * and everything's fine as long as they're <= the pnodes'
-	       * feature weight.
-	       */
-	      double oldvalue;
-	      oldvalue = feature_it->second; 
-	      feature_it->second -= desire_it->second;
-	      if ((oldvalue >= 0) && (feature_it->second < 0)) {
-		// This one pushed us over the edge, violation!
-		score += SCORE_OVERUSED_LOCAL_FEATURE;
-		out_fd_violated++;
-	      }
-	      break;
-	    default:
-	      // Local features are required to have some kind of type
-	      cout << "Bad local desire " << (*desire_it).first << endl;
-	      exit(EXIT_FATAL);
-	    }
-	}
-      }
-    }
-  }
-
-  return score;
-}
-
-double remove_stateful_fds(tb_vnode *vnode, tb_pnode *pnode,
-                           int &out_fd_violated) {
-  tb_vnode::desires_map::iterator desire_it;
-  double score = 0.0f;
-  if (!vnode->desires.empty()) {
-    for (desire_it = vnode->desires.begin();
-	desire_it != pnode->features.end();++desire_it) {
-      if (desire_it->first[0] == '?') {
-	// Found a local desire - does the pnode have it?
-	tb_pnode::features_map::iterator feature_it;
-	feature_it = pnode->features.find(desire_it->first);
-	if (feature_it == pnode->features.end()) {
-	  // Didn't find the feature, so we just removed a violation (note -
-	  // out_fd_violted gets subtracted from the total violation count, so
-	  // adding to it causes a violation to be removed.)
-	  score += SCORE_MISSING_LOCAL_FEATURE;
-	  out_fd_violated++;
-	} else {
-	  // Found the feature, score it
-	  switch (((*desire_it).first)[1]) {
-	    case '+':
-	      /*
-	       * A 'sum' local feature - we add up all of the vnode desires,
-	       * and everything's fine as long as they're <= the pnodes'
-	       * feature weight.
-	       */
-	      double oldvalue;
-	      oldvalue = feature_it->second; 
-	      feature_it->second += desire_it->second;
-	      if ((oldvalue < 0) && (feature_it->second >= 0)) {
-		// This one pushed us over the edge, we removed a violation
-		// (note - out_fd_violted gets subtracted from the total
-		// violation count, so adding to it causes a violation to be
-		// removed.)
-		score += SCORE_OVERUSED_LOCAL_FEATURE;
-		out_fd_violated++;
-	      }
-	      break;
+		if (fdit->is_local()) {
+		    // We handle local features specially
+		    score_delta += SCORE_MISSING_LOCAL_FEATURE;
+		} else {
+		    score_delta += fdit->cost();
+		}
+		break;
+	    case tb_featuredesire_set_iterator::SECOND_ONLY:
+		/*
+		 * On the pnode, but not the vnode
+		 */
+		// What we do here depends on what kind o feature it is
+		if (fdit->is_local()) { 
+		    // Do nothing for local features that are not matched -
+		    // they are free to waste
+		} else if (fdit->is_global()) {
+		    // Global feature - we have to look at what others are in
+		    // use
+		    score_and_violations delta;
+		    if (add) {
+			delta = fdit->add_global_user();
+		    } else {
+			delta = fdit->remove_global_user();
+		    }
+		    score_delta += delta.first;
+		    violations_delta += delta.second;
+		} else {
+		    // Regular feature - score the sucker
+		    score_delta += fdit->cost();
+		    if (fdit->is_violateable()) {
+			violations_delta++;
+		    }
+		}
+		break;
 	    default:
-	      // Local features are required to have some kind of type
-	      cout << "Bad local desire " << (*desire_it).first << endl;
-	      exit(EXIT_FATAL);
-	    }
+		cerr << "*** Internal error - bad set membership" << endl;
+		exit(EXIT_FATAL);
 	}
-      }
     }
-  }
 
-  return score;
+    // Okay, return the score and violations
+    //cerr << "Returning (" << score_delta << "," << violations_delta << ")" <<
+    //   endl;
+    return score_and_violations(score_delta,violations_delta);
 }
 
-/*
- * For now, in these function, which simply keep the global_fd_set
- * data structure up to date, we ignore vnodes desires - however, we may
- * decide to change this someday if we use global features for some other
- * purpose
- */
-void add_global_fds(tb_vnode *vnode,tb_pnode *pnode) {
-  tb_pnode::features_map::iterator feature_it;
-  if (!pnode->features.empty()) {
-    for (feature_it = pnode->features.begin();
-	feature_it != pnode->features.end();++feature_it) {
-      if (feature_it->first[0] == '*') {
-	global_fd_set[feature_it->first]++;
-      }
-    }
-  }
-}
-void remove_global_fds(tb_vnode *vnode,tb_pnode *pnode) {
-  tb_pnode::features_map::iterator feature_it;
-  if (!pnode->features.empty()) {
-    for (feature_it = pnode->features.begin();
-	feature_it != pnode->features.end();++feature_it) {
-      if (feature_it->first[0] == '*') {
-	global_fd_set[feature_it->first]--;
-	assert(global_fd_set[feature_it->first] >= 0);
-      }
-    }
-  }
-}
diff --git a/assign/solution.cc b/assign/solution.cc
index b75d4c1df28cab8c7383155ee5003e3492aeb851..b91008d47f487e6278039fb8141891f299e1fd8f 100644
--- a/assign/solution.cc
+++ b/assign/solution.cc
@@ -164,12 +164,14 @@ void print_solution_summary()
       }
 
       // Print out used local additive features
-      tb_pnode::features_map::iterator feature_it;
+      node_feature_set::iterator feature_it;
       for (feature_it = pnode->features.begin();
 	  feature_it != pnode->features.end();++feature_it) {
-	if ((feature_it->first[0] == '?') && (feature_it->first[1] == '+')) {
-	  double remaining = feature_it->second; 
-	  cout << "    " << feature_it->first << ":" << remaining << endl;
+	if (feature_it->is_local() && feature_it->is_l_additive()) {
+	    double total = feature_it->cost();
+	    double used = feature_it->used();
+	  cout << "    " << feature_it->name() << ": used=" << used <<
+	      " total=" << total << endl;
 	}
       }
     }
diff --git a/assign/virtual.h b/assign/virtual.h
index b07e4b6b872f208f99ac7e2ef4342c73e69d2232..a9035682f1a7a8b190dbebf3681081234bd03b80 100644
--- a/assign/virtual.h
+++ b/assign/virtual.h
@@ -21,6 +21,7 @@
 #include <list>
 using namespace std;
 
+#include "featuredesire.h"
 
 class tb_plink;
 class tb_vnode;
@@ -89,19 +90,16 @@ public:
     o << "tb_vnode: " << node.name << " (" << &node << ")" << endl;
     o << "  Type: " << node.type <<  " Count: " << node.typecount << endl;
     o << "  Desires:" << endl;
-    for (desires_map::const_iterator it = node.desires.begin();
+    for (node_desire_set::const_iterator it = node.desires.begin();
 	 it!=node.desires.end();it++) 
-      o << "    " << (*it).first << " -> " << (*it).second << endl;
+      o << "    " << it->name() << " -> " << it->cost() << endl;
     o << " vclass=" << node.vclass << " fixed=" <<
       node.fixed << endl;
     return o;
   }
 
-  typedef hash_map<crope,double> desires_map;
-  typedef hash_map<crope,int> desires_count_map;
-
   // contains weight of each desire
-  desires_map desires;
+  node_desire_set desires;
 
   crope type;			// the current type of the node
   int typecount;		// How many slots of the type this vnode takes up