Binpack.java

/**
 * Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
 * All Rights Reserved.
 *
 * NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
 * OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
 * MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 *
 * This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 * SPDX-License-Identifier: EPL-2.0
 *
 * Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
 *
 * This program includes and/or can make use of certain third party source code, object code, documentation and other
 * files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
 * configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
 * conditions contained in any such Third Party Software or separate license file distributed with such Third Party
 * Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
 * aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
 * censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
 */
package org.osate.analysis.resource.management.handlers;

import java.util.Collection;
import java.util.Collections;
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 org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.ComponentClassifier;
import org.osate.aadl2.DataClassifier;
import org.osate.aadl2.Element;
import org.osate.aadl2.Feature;
import org.osate.aadl2.ListValue;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.ProcessorClassifier;
import org.osate.aadl2.PropertyExpression;
import org.osate.aadl2.RecordValue;
import org.osate.aadl2.SystemClassifier;
import org.osate.aadl2.contrib.aadlproject.SizeUnits;
import org.osate.aadl2.contrib.aadlproject.TimeUnits;
import org.osate.aadl2.contrib.timing.TimingProperties;
import org.osate.aadl2.contrib.util.AadlContribUtils;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.ConnectionInstance;
import org.osate.aadl2.instance.ConnectionKind;
import org.osate.aadl2.instance.FeatureInstance;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.InstanceReferenceValue;
import org.osate.aadl2.instance.ModeInstance;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.instance.util.InstanceUtil;
import org.osate.aadl2.modelsupport.errorreporting.AnalysisErrorReporterManager;
import org.osate.aadl2.modelsupport.modeltraversal.ForAllElement;
import org.osate.aadl2.properties.InvalidModelException;
import org.osate.aadl2.properties.PropertyNotPresentException;
import org.osate.ui.handlers.AbstractInstanceOrDeclarativeModelReadOnlyHandler;
import org.osate.xtext.aadl2.properties.util.GetProperties;
import org.osate.xtext.aadl2.properties.util.InstanceModelUtil;
import org.osate.xtext.aadl2.properties.util.PropertyUtils;

import EAnalysis.BinPacking.AssignmentResult;
import EAnalysis.BinPacking.BFCPBinPacker;
import EAnalysis.BinPacking.BandwidthComparator;
import EAnalysis.BinPacking.CapacityComparator;
import EAnalysis.BinPacking.CompositeSoftNode;
import EAnalysis.BinPacking.DFBPBinPacker;
import EAnalysis.BinPacking.DFCPBinPacker;
import EAnalysis.BinPacking.Disjoint;
import EAnalysis.BinPacking.Expansor;
import EAnalysis.BinPacking.HardwareNode;
import EAnalysis.BinPacking.LowLevelBinPacker;
import EAnalysis.BinPacking.Message;
import EAnalysis.BinPacking.NFCHoBinPacker;
import EAnalysis.BinPacking.NoExpansionExpansor;
import EAnalysis.BinPacking.OutDegreeAssignmentProblem;
import EAnalysis.BinPacking.OutDegreeComparator;
import EAnalysis.BinPacking.SetConstraint;
import EAnalysis.BinPacking.Site;
import EAnalysis.BinPacking.SiteArchitecture;
import EAnalysis.BinPacking.SiteGuest;
import EAnalysis.BinPacking.SoftwareNode;

/**
 * Handler performs a binpacking on all the threads in a given system.  Tries
 * to map them to the processors based on their allowed processor bindings
 * and not_collocated property values.
 *
 * <p>This used to extend AaxlModifyAction, but (1) that class seems to be
 * broken right now, and (2) we don't want to automatically save the resource
 * because we are going to change it using an command via the core editor.
 */
public class Binpack extends AbstractInstanceOrDeclarativeModelReadOnlyHandler {
	private static final int IMMEDIATE_PARTITION = 0;
	private static final int DEFER_EXEC_TIME = 1;
	private static final int DEFER_BANDWIDTH = 2;

	static final double defaultMIPS = 1000.0;

	private int partitionChoice = 2;

	@Override
	public String getMarkerType() {
		return "org.osate.analysis.resource.management.BinpackObjectMarker";
	}

	@Override
	protected String getActionName() {
		return "Bind threads to processors";
	}

	// Don't allow analysis in all modes.
	@Override
	protected boolean analyzeInSingleModeOnly() {
		return true;
	}

	@Override
	protected boolean initializeAction(NamedElement obj) {
		setCSVLog("Binpacking", obj);

		// Select the bin packing strategy
		partitionChoice = org.osate.ui.dialogs.Dialog.askQuestion("Choose partitioning algorithm",
				"This bin packing algorithm groups threads that "
						+ "communicate with each other in groups and try to fit them "
						+ "together. If it is not possible it will partition the groups.\n\n"
						+ "Two main partitioning strategies exist: \n"
						+ "(1) Partition group as soon as they are discovered not to fit.\n"
						+ "(2) Put them aside and continue packing all groups that fit and after "
						+ "partition those that did not fit. Additionally it can select the "
						+ "group to partition based on the amount of data being communicated or "
						+ "the amount of computing cycles required by the group.",
				new String[] { "Immediate Partition of Groups", "Defer Partition of Groups Based on Exec. Time",
						"Defer Partition of Groups Based on Comm. Bandwidth" },
				IMMEDIATE_PARTITION);
		return partitionChoice != -1;
	}

	@Override
	protected boolean canAnalyzeDeclarativeModels() {
		return false;
	}

	@Override
	protected void analyzeDeclarativeModel(IProgressMonitor monitor, AnalysisErrorReporterManager errManager,
			Element declarativeObject) {
	}

	protected void checkBuses(ComponentInstance obj) {
		final RecordValue transTime = GetProperties.getTransmissionTime(obj);
		if (transTime == null) {
			logWarning("Bus " + obj.getComponentInstancePath()
					+ " is missing Transmission Time property. Using default of " + AADLBus.DEFAULT_TRANSMISSION_TIME);
		}

	}

	public void invoke(IProgressMonitor monitor, SystemInstance root, SystemOperationMode som) {
		invoke(monitor, AnalysisErrorReporterManager.NULL_ERROR_MANANGER, root, som);
	}

	public void invoke(final IProgressMonitor monitor, final AnalysisErrorReporterManager errManager,
			final SystemInstance root, final SystemOperationMode som) {
		this.errManager = errManager != null ? errManager
				: new AnalysisErrorReporterManager(getAnalysisErrorReporterFactory());
		analyzeInstanceModel(monitor, this.errManager, root, som);
	}

	@Override
	protected void analyzeInstanceModel(final IProgressMonitor monitor, final AnalysisErrorReporterManager errManager,
			final SystemInstance root, final SystemOperationMode som) {
		try {
			monitor.beginTask("Binding threads to processors in " + root.getName(), IProgressMonitor.UNKNOWN);

			logInfo("Binpacker Analysis Report\n");
			/*
			 * Verify that all the busses have a transmission time
			 */
			final ForAllElement addBuses = new ForAllElement(errManager) {
				@Override
				public void process(Element obj) {
					checkBuses((ComponentInstance) obj);
				}
			};
			addBuses.processPreOrderComponentInstance(root, ComponentCategory.BUS);

			/*
			 * Find and report all thread and device instances that don't have a
			 * period specified.
			 */
			EList<Element> incompletethreads = new ForAllElement() {
				@Override
				protected boolean suchThat(Element obj) {
					final ComponentCategory cat = ((ComponentInstance) obj).getCategory();
					if (cat == ComponentCategory.THREAD || cat == ComponentCategory.DEVICE) {
						return org.osate.pluginsupport.properties.PropertyUtils
						.getScaled(TimingProperties::getPeriod, (ComponentInstance) obj, TimeUnits.MS).orElse(0.0) == 0.0;
					} else {
						return false;
					}
				}
			}.processPreOrderComponentInstance(root);
			for (final Iterator<Element> i = incompletethreads.iterator(); i.hasNext();) {
				final ComponentInstance o = (ComponentInstance) i.next();
				logWarning((InstanceModelUtil.isThread(o) ? "Thread " : "Device ") + o.getComponentInstancePath()
						+ " is missing period property. Using default of 1 ns");
			}

			/*
			 * Find and report all thread instances that don't have a
			 * compute execution time specified.
			 */
			incompletethreads = new ForAllElement() {
				@Override
				protected boolean suchThat(Element obj) {
					return GetProperties.getThreadExecutioninMilliSec((ComponentInstance) obj) == 0.0;
				}
			}.processPreOrderComponentInstance(root, ComponentCategory.THREAD);
			for (final Iterator<Element> i = incompletethreads.iterator(); i.hasNext();) {
				final ComponentInstance o = (ComponentInstance) i.next();
				logWarning("Thread " + o.getComponentInstancePath()
						+ " is missing compute_execution_time or InstructionsPerDispatch property. Using default of 0 ns");
			}

			/*
			 * Find if all the port connections have data size
			 */
			final ForAllElement addThreadConnections = new ForAllElement(errManager) {
				@Override
				public void process(Element obj) {
					if (obj instanceof ConnectionInstance) {
						final ConnectionInstance connInst = (ConnectionInstance) obj;
						if (connInst.getKind() == ConnectionKind.PORT_CONNECTION) {
							final FeatureInstance src = (FeatureInstance) connInst.getSource();

							Feature srcAP = src.getFeature();
							Classifier cl = srcAP.getClassifier();
							if (cl instanceof DataClassifier) {
								DataClassifier srcDC = (DataClassifier) cl;
								if (AadlContribUtils.getDataSize(srcDC, SizeUnits.BYTES) == 0) {
									logWarning("Data size of connection source port " + src.getComponentInstancePath()
											+ " not specified");
								}
							}
						}
					}
				}
			};
			addThreadConnections.processPreOrderAll(root);

			/* The partitionChoice is set in initializeANalysis() */
			NoExpansionExpansor expansor = new NoExpansionExpansor();
			LowLevelBinPacker packer = null;
			if (partitionChoice == IMMEDIATE_PARTITION) {
				packer = new BFCPBinPacker(expansor);
			} else if (partitionChoice == DEFER_EXEC_TIME) {
				packer = new DFCPBinPacker(expansor);
			} else if (partitionChoice == DEFER_BANDWIDTH) {
				packer = new DFBPBinPacker(expansor);
			}

			AssignmentResult result = binPackSystem(root, expansor, packer, errManager, som);

			reportResults(som, result);

			if (result.isSuccess()) {
				showResults(som, root, result);
			} else {
				showNoResults(som);
			}
		} catch (InvalidModelException e) {
			error(e.getElement(), e.getMessage());
		}
	}

	protected EList<Element> getExecutionUnits(SystemInstance root) {
		return new ForAllElement().processPreOrderComponentInstance(root, ComponentCategory.THREAD);
	}

	boolean existsProcessorWithMIPS = false;
	boolean existsProcessorWithoutMIPS = false;
	boolean existsThreadWithReferenceProcessor = false;
	boolean existsThreadWithoutReferenceProcessor = false;

	protected AssignmentResult binPackSystem(final SystemInstance root, Expansor expansor, LowLevelBinPacker packer,
			final AnalysisErrorReporterManager errManager, final SystemOperationMode som) {

		existsProcessorWithMIPS = false;
		existsProcessorWithoutMIPS = false;
		existsThreadWithReferenceProcessor = false;
		existsThreadWithoutReferenceProcessor = false;

		/*
		 * Map from AADL ComponentInstances representing threads to
		 * the bin packing SoftwareNode that models the thread.
		 */
		final Map<ComponentInstance, AADLThread> threadToSoftwareNode = new HashMap<>();
		/*
		 * Set of thread components. This is is the keySet of
		 * threadToSoftwareNode.
		 */
		final Set<ComponentInstance> threads = threadToSoftwareNode.keySet();
		/*
		 * Map from AADL ComponentInstances representing threads to
		 * the set of AADL ComponentInstances that cannot be collocated
		 * with it.
		 */
		final Map<ComponentInstance, Set<ComponentInstance>> notCollocated = new HashMap<>();

		/*
		 * Map from AADL ComponentInstance representing processors to
		 * the bin packing Processor that models them.
		 */
		final Map<ComponentInstance, AADLProcessor> procToHardware = new HashMap<>();

		/*
		 * Map from AADL BusInstance representing Buses to
		 * The bin packing Link that models them.
		 */
		final Map<ComponentInstance, AADLBus> busToHardware = new HashMap<>();

		/*
		 * One site to rule them all! We don't care about the site
		 * architecture, so just create one site to hold everything.
		 * We aren't worried about power or space issues either, so
		 * we just set them to 100.0 because those are nice values.
		 * The site accepts AADL processors.
		 */
		final SiteArchitecture siteArchitecture = new SiteArchitecture();
		AADLProcessor ap = AADLProcessor.PROTOTYPE;
		final Site theSite = new Site(100.0, 100.0, new SiteGuest[] { ap });
		siteArchitecture.addSite(theSite);

		/*
		 * The hardware is fixed based on the AADL specification, so we
		 * use the NoExpansionExpansor to keep the hardware from being
		 * generated for us.
		 */

		expansor.setSiteArchitecture(siteArchitecture);

		/*
		 * Populate the problem space based on the AADL specification. First
		 * we walk the instance model and add all the processors. Then we
		 * walk the instance model again to add all the threads.
		 */
		OutDegreeAssignmentProblem problem1 = new OutDegreeAssignmentProblem(new OutDegreeComparator(),
				new BandwidthComparator(), new CapacityComparator());
		problem1.setErrorReporter(new BinPackErrorReporter());
		final OutDegreeAssignmentProblem problem = problem1;
		// Add procs
		final ForAllElement addProcessors = new ForAllElement(errManager) {
			@Override
			public void process(Element obj) {
				ComponentInstance ci = (ComponentInstance) obj;
				// the createInstance method already assigns a default MIPS if none exists
				double mips = GetProperties.getProcessorMIPS(ci);
				// checking consistency;
				existsProcessorWithMIPS |= (mips != 0);
				existsProcessorWithoutMIPS |= (mips == 0);

				final AADLProcessor proc = AADLProcessor.createInstance(ci);
				if (proc != null) {
					System.out.println("Processor cycles Per sec:" + proc.getCyclesPerSecond());
					siteArchitecture.addSiteGuest(proc, theSite);
					problem.getHardwareGraph().add(proc);
					// add reverse mapping
					procToHardware.put(ci, proc);
				}
			}
		};
		addProcessors.processPreOrderComponentInstance(root, ComponentCategory.PROCESSOR);

		/*
		 * Get all the links
		 */

		final ForAllElement addBuses = new ForAllElement(errManager) {
			@Override
			public void process(Element obj) {
				ComponentInstance bi = (ComponentInstance) obj;

				final AADLBus bus = AADLBus.createInstance(bi);
				busToHardware.put(bi, bus);
			}
		};

		addBuses.processPreOrderComponentInstance(root, ComponentCategory.BUS);

		/*
		 * create the links between processors and busses
		 * (i.e., process connections)
		 */
		for (final Iterator<ConnectionInstance> i = root.getAllConnectionInstances().iterator(); i.hasNext();) {
			final ConnectionInstance connInst = i.next();
			if (connInst.getKind() == ConnectionKind.ACCESS_CONNECTION) {
				InstanceObject src = connInst.getSource();
				InstanceObject dst = connInst.getDestination();

				AADLBus bus = null;
				AADLProcessor processor = null;

				// swap if i got them in the opposite order
				if (src instanceof FeatureInstance) {
					InstanceObject tmp = dst;
					dst = src;
					src = tmp;
				}

				bus = busToHardware.get(src);
				FeatureInstance fi = (FeatureInstance) dst;

				processor = procToHardware.get(fi.getContainingComponentInstance());

				if (bus != null && processor != null) {
					bus.add(processor);
					processor.attachToLink(bus);
				}
			}
		}

		// Now add all the links so the connectivity matrix in the problem is
		// updated correctly

		for (Iterator<AADLBus> iBus = busToHardware.values().iterator(); iBus.hasNext();) {
			AADLBus bus = iBus.next();
			problem.addLink(bus);
			siteArchitecture.addSiteGuest(bus, theSite);
		}

		// Add threads
		final ForAllElement addThreads = new ForAllElement(errManager) {
			@Override
			public void process(Element obj) {
				final ComponentInstance ci = (ComponentInstance) obj;

				/**
				 * JD - check the modes according to what was
				 * suggested by Dave.
				 */
				boolean selected = true;

				if (som.getCurrentModes().size() > 0) {
					selected = false;
					for (ModeInstance mi : ci.getInModes()) {
						if (mi == som.getCurrentModes().get(0)) {
							selected = true;
						}
					}
				}

				if (!selected) {
					return;
				}

				final AADLThread thread = AADLThread.createInstance(ci);
				double refmips = GetProperties.getReferenceMIPS(ci);

				// validate consistency
				existsThreadWithReferenceProcessor |= (refmips != 0);
				existsThreadWithoutReferenceProcessor |= (refmips == 0);

				problem.getSoftwareGraph().add(thread);
//				logInfo(thread.getReport());
				// add reverse mapping
				threadToSoftwareNode.put(ci, thread);

				// Process NOT_COLLOCATED property.
				RecordValue disjunctFrom = GetProperties.getNotCollocated(ci);
				if (disjunctFrom == null) {
					return;
				}
				final Set<ComponentInstance> disjunctSet = new HashSet<>();
				ListValue tvl = (ListValue) PropertyUtils.getRecordFieldValue(disjunctFrom, "Targets");
				for (PropertyExpression ref : tvl.getOwnedListElements()) {
					/*
					 * Add all the instances rooted at the named instance.
					 * For example, the thread may be declared to be disjunct
					 * from another process, so we really want to be disjunct
					 * from the other threads contained in that process.
					 */
					final InstanceReferenceValue rv = (InstanceReferenceValue) ref;
					final ComponentInstance refCI = (ComponentInstance) rv.getReferencedInstanceObject();
					disjunctSet.addAll(refCI.getAllComponentInstances());
				}
				if (!disjunctSet.isEmpty()) {
					notCollocated.put(ci, disjunctSet);
				}
			}
		};
		addThreads.processPreOrderComponentInstance(root, ComponentCategory.THREAD);

		/*
		 * MIPS consistency rules:
		 *
		 * Either all threads have reference processor with mips and all processors have mips spec or
		 * None of the threads and processors have any mips specification and we can use the default mips
		 */

		// only some processors have mips
		if (existsProcessorWithMIPS && existsProcessorWithoutMIPS) {
			errManager.error(root, "Not all processors have MIPSCapacity");
			return null;
		}
		// only some threads with reference processor
		if (existsThreadWithReferenceProcessor && existsThreadWithoutReferenceProcessor) {
			errManager.error(root, "Not all threads have execution time reference processor");
			return null;
		}

		// threads and processors mips spec not consistent
		if (existsProcessorWithMIPS && existsThreadWithoutReferenceProcessor) {
			errManager.error(root,
					"There are some processors with MIPSCapacity but some threads without execution time reference processors");
			return null;
		}

		if (existsProcessorWithoutMIPS && existsThreadWithReferenceProcessor) {
			errManager.error(root,
					"There are some threads with execution time reference processors but not all processors have MIPSCapacity");
			return null;
		}

		// Add thread connections (Messages)
		for (final Iterator<ConnectionInstance> i = root.getAllConnectionInstances().iterator(); i.hasNext();) {
			final ConnectionInstance connInst = i.next();
			if (connInst.getKind() == ConnectionKind.PORT_CONNECTION) {
				if (!(connInst.getSource() instanceof FeatureInstance
						&& connInst.getDestination() instanceof FeatureInstance)) {
					continue;
				}
				final FeatureInstance src = (FeatureInstance) connInst.getSource();
				final FeatureInstance dst = (FeatureInstance) connInst.getDestination();

				final ComponentInstance ci = src.getContainingComponentInstance();
				AADLThread t1 = threadToSoftwareNode.get(ci);
				AADLThread t2 = threadToSoftwareNode.get(dst.getContainingComponentInstance());
				if (t1 != null && t2 != null) {
					Feature srcAP = src.getFeature();
					// TODO: get the property directly
					Classifier cl = srcAP.getClassifier();
					if (cl instanceof DataClassifier) {
						DataClassifier srcDC = (DataClassifier) cl;
						double dataSize = 0.0;
						double threadPeriod = 0.0;
						try {
							dataSize = AadlContribUtils.getDataSize(srcDC, SizeUnits.BYTES);
						} catch (Exception e) {
							errManager.warning(connInst, "No Data Size for connection");
						}
						try {
							threadPeriod = GetProperties.getPeriodinNS(ci);
						} catch (Exception e) {
							errManager.warning(connInst, "No Period for connection");
						}

						// Now I can create the Message

						Message msg = new Message((long) dataSize, (long) threadPeriod, (long) threadPeriod, t1, t2);
						System.out.println(">>>>>>>>>> Adding message (" + Long.toString((long) dataSize) + "/"
								+ Long.toString((long) threadPeriod) + ") between " + t1.getName() + " and "
								+ t2.getName() + " based on connection " + connInst.getName());
						problem.addMessage(msg);
					} else {
						errManager.warning(connInst, "No Data Classifier for connection");
					}
				}
			}
		}

		// Add collocation constraints
		for (final Iterator<ComponentInstance> constrained = notCollocated.keySet().iterator(); constrained.hasNext();) {
			final ComponentInstance ci = constrained.next();
			final SoftwareNode sn = threadToSoftwareNode.get(ci);
			final Set<ComponentInstance> disjunctFrom = notCollocated.get(ci);
			for (final Iterator<ComponentInstance> dfIter = disjunctFrom.iterator(); dfIter.hasNext();) {
				/*
				 * Items in the disjunctFrom set do not have to be thread
				 * instances because of the way we add items to it (see above).
				 * We are only interested in the thread instances here, in
				 * particular because we only create SoftwareNodes for the
				 * thread instances, and we don't want to get null return
				 * values from the threadToSoftwareNode map.
				 */
				final ComponentInstance ci2 = dfIter.next();
				if (ci2.getCategory() == ComponentCategory.THREAD) {
					final SoftwareNode sn2 = threadToSoftwareNode.get(ci2);
					final SoftwareNode[] disjunction = new SoftwareNode[] { sn, sn2 };
					problem.addConstraint(new Disjoint(disjunction));
				}
			}
		}

		/*
		 * Add Allowed_Processor_Binding and
		 * Allowed_Processor_Binding_Class constraints
		 */
		for (final Iterator<ComponentInstance> i = threads.iterator(); i.hasNext();) {
			final ComponentInstance thr = i.next();
			final SoftwareNode thrSN = threadToSoftwareNode.get(thr);
			Collection<ComponentInstance> allowed = getActualProcessorBindings(thr);
			if (allowed.size() == 0) {
				allowed = getAllowedProcessorBindings(thr);
			}
			if (allowed.size() > 0) {
				final Object[] allowedProcs = new Object[allowed.size()];
				int idx = 0;
				for (Iterator<ComponentInstance> j = allowed.iterator(); j.hasNext(); idx++) {
					final ComponentInstance proc = j.next();
					allowedProcs[idx] = procToHardware.get(proc);
				}
				problem.addConstraint(new SetConstraint(new SoftwareNode[] { thrSN }, allowedProcs));
			}
		}

		// Try to bin pack
		final NFCHoBinPacker highPacker = new NFCHoBinPacker(packer);
		final boolean res = highPacker.solve(problem);
		return new AssignmentResult(problem, res);
	}

	private abstract static class ShowDialog implements Runnable {
		protected volatile int result;
	}

	String getBindingText(final Map threadsToProc) {
		String bindings = "";
		for (Iterator iter = threadsToProc.keySet().iterator(); iter.hasNext();) {
			final ComponentInstance thread = (ComponentInstance) iter.next();
			final ComponentInstance proc = (ComponentInstance) threadsToProc.get(thread);
			bindings += "Actual_Processor_Binding => reference (" + proc.getInstanceObjectPath() + ") applies to "
					+ thread.getInstanceObjectPath() + ";\n";
		}

		return bindings;
	}

	public void showResults(final SystemOperationMode som, final SystemInstance root, final AssignmentResult result) {
		final Map threadsToProc = getThreadBindings(result.getProblem().getHardwareGraph());

		final String propText = getBindingText(threadsToProc);
		boolean done = false;
		final Shell sh = getShell();
		while (sh != null && !done) {
			final Dialog d = new PackingSuccessfulDialog(getShell(), som, root.getComponentImplementation().getName(),
					threadsToProc, result.getProblem().getHardwareGraph(), propText);
			final ShowDialog sd = new ShowDialog() {
				@Override
				public void run() {
					this.result = d.open();
				}
			};
			Display.getDefault().syncExec(sd);

			if (sd.result == PackingSuccessfulDialog.INSTANCE_ID) {
				setInstanceModelBindings(root, threadsToProc);
			}
			// XXX: Don't set properties in the declarative model any more?
//				else if (button == PackingSuccessfulDialog.DECLARATIVE_ID) {
//					setDeclarativeBindings(root, threadsToProc);
//				}
			else {
				done = true;
			}
		}
	}

	public void showNoResults(final SystemOperationMode som) {
		org.osate.ui.dialogs.Dialog.showError("Application Binding Results",
				"In system operation mode " + som.getName() + "the application system is not schedulable");
	}

	private Map getThreadBindings(final Set hardware) {
		final Map threadsToProc = new HashMap();
		for (Iterator iter = hardware.iterator(); iter.hasNext();) {
			HardwareNode n = (HardwareNode) iter.next();
			for (Iterator taskSet = n.getTaskSet().iterator(); taskSet.hasNext();) {
				SoftwareNode m = (SoftwareNode) taskSet.next();
				if (m instanceof CompositeSoftNode) {
					final Set set = ((CompositeSoftNode) m).getBasicComponents();
					for (Iterator software = set.iterator(); software.hasNext();) {
						final SoftwareNode sn = (SoftwareNode) software.next();
						threadsToProc.put(sn.getSemanticObject(), n.getSemanticObject());
					}
				} else {
					if (!(m instanceof Message)) {
						threadsToProc.put(m.getSemanticObject(), n.getSemanticObject());
					}
				}
			}
		}
		return threadsToProc;
	}

	public void reportResults(SystemOperationMode som, final AssignmentResult result) {
		final Map threadsToProc = getThreadBindings(result.getProblem().getHardwareGraph());

		logInfo("\nBinpacking results"
				+ (!som.getName().equalsIgnoreCase("No Modes") ? " for SOM " + som.getName() : "") + ": "
				+ (result.isSuccess() ? "Success" : "FAILED"));
		for (final Iterator i = result.getProblem().getHardwareGraph().iterator(); i.hasNext();) {
			final HardwareNode hn = (HardwareNode) i.next();
			final ComponentInstance proc = (ComponentInstance) hn.getSemanticObject();
			double load = hn.getCyclesPerSecond() - hn.getAvailableCapacity();
			load /= hn.getCyclesPerSecond();
			load *= 100.0;
			long longLoad = (long) Math.ceil(load);
			double overload = (hn.getCyclesPerSecond() - hn.getAvailableCapacity()) - (hn.getCyclesPerSecond());
			overload /= hn.getCyclesPerSecond();
			overload *= 100.0;
			long longOverload = (long) Math.ceil(overload);
			long available = longOverload * -1;
			logInfo("Processor " + proc.getInstanceObjectPath() + " (" + hn.getCyclesPerSecond() / 1000000 + " MIPS) Load: "
					+ Long.toString(longLoad) + "%" + " Available: " + Long.toString(available) + "%");
		}
		logInfo("\nThread to Processor Bindings");
		for (Iterator iter = threadsToProc.keySet().iterator(); iter.hasNext();) {
			final ComponentInstance thread = (ComponentInstance) iter.next();
			final ComponentInstance proc = (ComponentInstance) threadsToProc.get(thread);
			double threadMips = GetProperties.getThreadExecutioninMIPS(thread);
			double cpumips = GetProperties.getMIPSCapacityInMIPS(proc, 0);

			logInfo("Thread " + thread.getInstanceObjectPath() + " ==> Processor " + proc.getInstanceObjectPath()
					+ (cpumips > 0 ? (" Utilization " + threadMips / cpumips * 100 + "%") : " No CPU capacity"));
		}
	}

	private void setInstanceModelBindings(final SystemInstance root, final Map threadsToProc) {
		final EditingDomain editingDomain = AdapterFactoryEditingDomain.getEditingDomainFor(root);
		if (editingDomain != null) {
			final CommandStack cmdStack = editingDomain.getCommandStack();
			final Command setBindings = new SetInstanceModelBindings(threadsToProc);
			cmdStack.execute(setBindings);
		} else {
			internalError("Couldn't get editing domain");
		}

		/*
		 * XXX: Keep this around for now. May want to keep the ability to
		 * modify the model directly so that we can use this action without
		 * using an editor.
		 */
//		for (Iterator iter = threadsToProc.keySet().iterator(); iter.hasNext(); ) {
//			final ComponentInstance thread = (ComponentInstance) iter.next();
//			final ComponentInstance proc = (ComponentInstance) threadsToProc.get(thread);
//			final InstanceReferenceValue val = InstanceFactory.eINSTANCE.createInstanceReferenceValue();
//			val.setReferencedInstanceObject(proc);
//			thread.setPropertyValue(Binpack.actualProcessorBinding, val);
//		}
	}

	/*
	 * We don't want to do this anymore? Keep the code around for the moment
	 * though.
	 */
//	/** @deprecated */
//	private void setDeclarativeBindings(
//			final SystemInstance root, final Map threadsToProc) {
//		final SystemImpl system = root.getSystemImpl();
//		for (Iterator iter = threadsToProc.keySet().iterator(); iter.hasNext(); ) {
//			final ComponentInstance thread = (ComponentInstance) iter.next();
//			final ComponentInstance proc = (ComponentInstance) threadsToProc.get(thread);
//			final ReferenceValue procRef = proc.getReferenceTo();
//			final List threadPath = thread.getReferencePathTo();
//			system.setContainedPropertyValue(
//					Binpack.actualProcessorBinding,
//					threadPath, procRef);
//		}
//	}

//	private Properties constructDeclarativeBindings(final Map threadsToProc) {
//		final Properties props = PropertyFactory.eINSTANCE.createProperties();
//		for (Iterator iter = threadsToProc.keySet().iterator(); iter.hasNext(); ) {
//			final ComponentInstance thread = (ComponentInstance) iter.next();
//			final ComponentInstance proc = (ComponentInstance) threadsToProc.get(thread);
//			final ReferenceValue procRef = proc.getReferenceTo();
//			final List threadPath = thread.getReferencePathTo();
//
//			// create the Property Association
//			final PropertyAssociation pa =
//				PropertyFactory.eINSTANCE.createPropertyAssociation();
//			properties.setActualProcessorBindingDefinitionToAssociation(pa);
//			for (final Iterator i = threadPath.iterator(); i.hasNext(); ) {
//				pa.addAppliesTo((PropertyHolder) i.next());
//			}
//			pa.addPropertyValue(procRef);
//			props.addPropertyAssociation(pa);
//		}
//		return props;
//	}

	/**
	 * Get the processor components that a given thread is allowed to be bound to
	 * based on the thread's ACTUAL_PROCESSOR_BINDING property value. The processors are
	 * search for in the same system instance that the given thread component is
	 * a part of.
	 *
	 * @param thread
	 *                 The thread.
	 * @return An unmodifiable set of processor ComponentInstances.
	 * @exception IllegalArgumentException
	 *                      Thrown if the category of the component instance
	 *                      referenced by <code>thread</code> is not
	 *                      {@link ComponentCategory#THREAD_LITERAL}.
	 */
	public Collection<ComponentInstance> getActualProcessorBindings(final ComponentInstance thread) {
		if (thread.getCategory() != ComponentCategory.THREAD) {
			throw new IllegalArgumentException("Component \"" + thread.getName() + "\" is not a thread.");
		}
		return InstanceModelUtil.getBoundPhysicalProcessors(thread);
	}

	/**
	 * Get the processor components that a given thread is allowed to be bound to
	 * based on the thread's ALLOWED_PROCESSOR_BINDING and
	 * ALLOWED_PROCESSOR_BINDING_CLASS property values. The processors are
	 * search for in the same system instance that the given thread component is
	 * a part of.
	 *
	 * @param thread
	 *                 The thread.
	 * @return An unmodifiable set of processor ComponentInstances.
	 * @exception IllegalArgumentException
	 *                      Thrown if the category of the component instance
	 *                      referenced by <code>thread</code> is not
	 *                      {@link ComponentCategory#THREAD_LITERAL}.
	 */
	public Set<ComponentInstance> getAllowedProcessorBindings(final ComponentInstance thread) {
		if (thread.getCategory() != ComponentCategory.THREAD) {
			throw new IllegalArgumentException("Component \"" + thread.getName() + "\" is not a thread.");
		}
		List<ComponentInstance> allowedBindingsVals;
		try {
			allowedBindingsVals = GetProperties.getAllowedProcessorBinding(thread);
		} catch (PropertyNotPresentException e) {
			// Ignore this situation and move on.
			allowedBindingsVals = Collections.emptyList();
		}
		List<Classifier> allowedClassVals;
		try {
			allowedClassVals = GetProperties.getAllowedProcessorBindingClass(thread);
		} catch (PropertyNotPresentException e) {
			// Ignore this situation and move on.
			allowedClassVals = Collections.emptyList();
		}
		final Set<ComponentInstance> searchRoots = new HashSet<>();
		if (allowedBindingsVals.isEmpty()) {
			searchRoots.add(thread.getSystemInstance());
		} else {
			for (final Iterator<ComponentInstance> i = allowedBindingsVals.iterator(); i.hasNext();) {
				final ComponentInstance rv = i.next();
				searchRoots.add(rv);
			}
		}
		final Set<SystemClassifier> allowedSystemClassifiers = new HashSet<>();
		final Set<ProcessorClassifier> allowedProcClassifiers = new HashSet<>();
		for (final Iterator<Classifier> i = allowedClassVals.iterator(); i.hasNext();) {
			final Classifier cc = i.next();
			if (cc instanceof ProcessorClassifier) { // ComponentCategory.PROCESSOR) {
				allowedProcClassifiers.add((ProcessorClassifier) cc);
			} else if (cc instanceof SystemClassifier) {// cv.getValue() == ComponentCategory.SYSTEM_LITERAL) {
				allowedSystemClassifiers.add((SystemClassifier) cc);
			} else {
				internalError(
						"Ill-formed allowed_processor_binding_class value: got a non-system non-processor component classifier");
			}
		}
		final Set<ComponentInstance> allowedProcs = new HashSet<>();
		for (final Iterator<ComponentInstance> i = searchRoots.iterator(); i.hasNext();) {
			final ComponentInstance ci = i.next();
			getAllowedProcessorBindings(ci, allowedProcs, allowedProcClassifiers, allowedSystemClassifiers);
		}
		return Collections.unmodifiableSet(allowedProcs);
	}

	/**
	 * Search the instance model structure rooted at the given component
	 * instance and add allowed processors to the given set of processors. A
	 * processor is added if it's component classifier matches the given set of
	 * processor classifiers (where the empty set means all processors). A
	 * system component instance is explored if it's component classifier
	 * matches the given set of system classifiers (where the empty set means
	 * all systems). This method does nothing if given a non-system,
	 * non-processor component instance.
	 *
	 * @param searchRoot
	 *                 The component instance to consider.
	 * @param allowedProcs
	 *                 The set of processors. This set is added to by this method.
	 * @param allowedProcClassifiers
	 *                 The set of component classifiers describing allowable
	 *                 processors.
	 * @param allowedSystemClassifiers
	 *                 The of component classifiers describing allowable systems.
	 */
	private void getAllowedProcessorBindings(final ComponentInstance searchRoot, final Set<ComponentInstance> allowedProcs,
			final Set<ProcessorClassifier> allowedProcClassifiers, final Set<SystemClassifier> allowedSystemClassifiers) {
		if (searchRoot.getCategory() == ComponentCategory.PROCESSOR) {
			/* If it's a processor, only add it if the classifier is okay. */
			if (testClassifier(searchRoot, allowedProcClassifiers)) {
				allowedProcs.add(searchRoot);
			}
		} else if (searchRoot.getCategory() == ComponentCategory.SYSTEM) {
			/*
			 * If it's a system then we look inside it for processors and other
			 * systems. But only look if the classifiers match.
			 */
			if (testClassifier(searchRoot, allowedSystemClassifiers)) {
				for (final Iterator<ComponentInstance> i = searchRoot.getComponentInstances().iterator(); i.hasNext();) {
					getAllowedProcessorBindings(i.next(), allowedProcs, allowedProcClassifiers,
							allowedSystemClassifiers);
				}
			}
		} else {
			// Do nothing, not interested in non-processor, non-system instance
		}
	}

	/**
	 * Test a component against a set of classifiers.
	 *
	 * @return Whether the component's classifier type is a descendent of any of
	 *         the given component classifiers. <em>Returns <code>true</code>
	 *         if the given set is empty!</em>
	 */
	public boolean testClassifier(ComponentInstance ci, final Set<? extends ComponentClassifier> classifiers) {
		if (classifiers.isEmpty()) {
			return true;
		}
		boolean match = false;
		ComponentClassifier cicc = InstanceUtil.getComponentClassifier(ci, 0, null);
		if (cicc == null) {
			return true;
		}
		for (final Iterator<? extends ComponentClassifier> i = classifiers.iterator(); i.hasNext() && !match;) {
			final ComponentClassifier cc = i.next();
			match = cicc.isDescendentOf(cc);
		}
		return match;
	}
}