private void extractReferenceFeatures(VTMatchSet matchSet, TaskMonitor monitor)
			throws CancelledException {

		srcVectorsByAddress = LazyMap.lazyMap(new HashMap<>(), addr -> new LSHCosineVectorAccum());
		destVectorsByAddress = LazyMap.lazyMap(new HashMap<>(), addr -> new LSHCosineVectorAccum());

		FunctionManager srcFuncManager = sourceProgram.getFunctionManager();
		FunctionManager destFuncManager = destinationProgram.getFunctionManager();
		int srcFunctionCount = srcFuncManager.getFunctionCount();
		int destFunctionCount = destFuncManager.getFunctionCount();

		Counter totalMatches = new Counter();
		Collection<VTMatchSet> matchSets = getMatchSets(matchSet.getSession(), totalMatches);
		monitor.initialize(totalMatches.count());

		// Loop through the matchSets in order to get total source and destination reference
		// counts that pass the filter
		Map<VTMatch, Set<Function>> sourceRefMap = new HashMap<>();
		Map<VTMatch, Set<Function>> destinationRefMap = new HashMap<>();

		for (VTMatchSet ms : matchSets) {
			Collection<VTMatch> matches = ms.getMatches();
			for (VTMatch match : matches) {

				monitor.checkCancelled();
				monitor.incrementProgress(1);

				accumulateMatchFunctionReferences(sourceRefMap, destinationRefMap, match);
			}
		}

		monitor.setMessage("Adding ACCEPTED matches to feature vectors.");
		int featureID = 1;

		// score each match that passed the filter above
		for (VTMatch match : sourceRefMap.keySet()) {

			monitor.checkCancelled();
			monitor.incrementProgress(1);

			if (sourceRefMap.get(match).isEmpty()) {
				continue;
			}

			/**
			 * Compute raw percentages for the sources and destination counts as ratios
			 * (total references to the match):(total number of references of the correct type)
			 */

			// Compute entropy of the system for the given match
			Set<Function> srcRefFuncs = new HashSet<>(sourceRefMap.get(match));
			Set<Function> destRefFuncs = new HashSet<>(destinationRefMap.get(match));

			// take the average probability that the feature appears in any one function (in either
			// source or dest)
			double altPraw = (double) (srcRefFuncs.size() + destRefFuncs.size()) /
				(srcFunctionCount + destFunctionCount);
			double weight = Math.sqrt(-Math.log(altPraw));

			// By the construction above, there may be duplicate functions in the RefMaps
			for (Function function : sourceRefMap.get(match)) {
				LSHCosineVectorAccum vector = srcVectorsByAddress.get(function.getEntryPoint());
				vector.addHash(featureID, weight);
			}

			for (Function function : destinationRefMap.get(match)) {
				LSHCosineVectorAccum vector = destVectorsByAddress.get(function.getEntryPoint());
				vector.addHash(featureID, weight);
			}

			++featureID;
		}

		updateSourceAndDestinationVectors(featureID, srcFuncManager, destFuncManager, monitor);
	}
extractReferenceFeatures is the core of the reference algorithm.  Each accepted match
becomes a unique feature. At the end, all the source and destination functions will have
"vectors" of these features, which are unique match ids.  Then the LSH dictionary can be
made from the source and we can look for matches in the destination.

@param matchSet the match set of previously user-accepted matches
@param monitor the monitor
Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/correlator/program/VTAbstractReferenceProgramCorrelator.java