private string[] GetLegendLabels(PlotDataType datatype)
        {
            string solverString = null;
            OpticalProperties opticalProperties = null;
            string modelString = null;
            ForwardSolverType forwardSolver;

            switch (datatype)
            {
                case PlotDataType.Simulated:
                    solverString = "\nSimulated:";
                    opticalProperties = MeasuredOpticalPropertyVM.GetOpticalProperties();
                    forwardSolver = MeasuredForwardSolverTypeOptionVM.SelectedValue;
                    break;
                case PlotDataType.Calculated:
                    solverString = "\nCalculated:";
                    opticalProperties = ResultOpticalPropertyVM.GetOpticalProperties();
                    forwardSolver = MeasuredForwardSolverTypeOptionVM.SelectedValue;
                    break;
                case PlotDataType.Guess:
                    solverString = "\nGuess:";
                    opticalProperties = InitialGuessOpticalPropertyVM.GetOpticalProperties();
                    forwardSolver = InverseForwardSolverTypeOptionVM.SelectedValue;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("datatype");
            }
            var opString = "\rμa=" + opticalProperties.Mua.ToString("F4") + " \rμs'=" + opticalProperties.Musp.ToString("F4");

            switch (forwardSolver)
            {
                case ForwardSolverType.DistributedGaussianSourceSDA:
                case ForwardSolverType.DistributedPointSourceSDA:
                case ForwardSolverType.PointSourceSDA:
                    modelString = "\rModel - SDA";
                    break;
                case ForwardSolverType.MonteCarlo:
                    modelString = "\rModel - scaled MC";
                    break;
                case ForwardSolverType.Nurbs:
                    modelString = "\rModel - nurbs";
                    break;
                case ForwardSolverType.TwoLayerSDA:
                    modelString = "\rModel - 2 layer SDA";
                    break;
            }
            
            if (_allRangeVMs.Length > 1)
            {
                var isWavelengthPlot = _allRangeVMs.Any(vm => vm.AxisType == IndependentVariableAxis.Wavelength);
                var secondaryRangeVM = isWavelengthPlot
                    ? _allRangeVMs.Where(vm => vm.AxisType != IndependentVariableAxis.Wavelength).First()
                    : _allRangeVMs.Where(vm => vm.AxisType != IndependentVariableAxis.Time && vm.AxisType != IndependentVariableAxis.Ft).First();

                string[] secondaryAxesStrings = secondaryRangeVM.Values.Select(value => "\r" + secondaryRangeVM.AxisType.GetInternationalizedString() + " = " + value.ToString()).ToArray();
                return secondaryAxesStrings.Select(sas => solverString + modelString + sas + (isWavelengthPlot ? "\r(spectral μa,μs')" : opString)).ToArray();
            }

            return new []{ solverString + modelString + opString };
        }
        private void SolveInverseCommand_Executed(object sender, ExecutedEventArgs e)
        {
            // Report inverse solver setup and results
            Commands.TextOutput_PostMessage.Execute("Inverse Solution Results: \r");
            Commands.TextOutput_PostMessage.Execute("   Optimization parameter(s): " + InverseFitTypeOptionVM.SelectedValue + " \r");
            Commands.TextOutput_PostMessage.Execute("   Initial Guess: " + InitialGuessOpticalPropertyVM + " \r");

            var inverseResult = SolveInverse();
            ResultOpticalPropertyVM.SetOpticalProperties(inverseResult.FitOpticalProperties.First()); // todo: this only works for one set of properties

            //Report the results
            if (SolutionDomainTypeOptionVM.IndependentVariableAxisOptionVM.SelectedValues.Contains(IndependentVariableAxis.Wavelength) && 
                inverseResult.FitOpticalProperties.Length > 1) // If multi-valued OPs, the results aren't in the "scalar" VMs, need to parse OPs directly
            {
                var fitOPs = inverseResult.FitOpticalProperties;
                var measuredOPs = inverseResult.MeasuredOpticalProperties;
                var wavelengths = GetParameterValues(IndependentVariableAxis.Wavelength);
                var wvUnitString = IndependentVariableAxisUnits.NM.GetInternationalizedString(); 
                var opUnitString = IndependentVariableAxisUnits.InverseMM.GetInternationalizedString();
                var sb = new StringBuilder("\t[Wavelength (" + wvUnitString + ")]\t\t\t\t\t\t[Exact]\t\t\t\t\t\t[At Converged Values]\t\t\t\t\t\t[Units]\r");
                for (int i = 0; i < fitOPs.Length; i++)
                {
                    sb.Append("\t" + wavelengths[i] + "\t\t\t\t\t\t" + measuredOPs[i] + "\t\t\t" + fitOPs[i] + "\t\t\t" + opUnitString + " \r");
                }
                Commands.TextOutput_PostMessage.Execute(sb.ToString());
            }
            else
            {
                Commands.TextOutput_PostMessage.Execute("   Exact: " + MeasuredOpticalPropertyVM + " \r");
                Commands.TextOutput_PostMessage.Execute("   At Converged Values: " + ResultOpticalPropertyVM + " \r");
            }

            PlotAxesLabels axesLabels = GetPlotLabels();
            Commands.Plot_SetAxesLabels.Execute(axesLabels);
            string[] plotLabels = GetLegendLabels(PlotDataType.Calculated);
            var plotData = Enumerable.Zip(inverseResult.FitDataPoints, plotLabels, (p, el) => new PlotData(p, el)).ToArray();
            Commands.Plot_PlotValues.Execute(plotData);
        }
        private void SolveInverseCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            // Report inverse solver setup and results
            WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute(StringLookup.GetLocalizedString("Label_InverseSolutionResults") + "\r");
            WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute("   " + StringLookup.GetLocalizedString("Label_OptimizationParameter") +
                                                                                InverseFitTypeOptionVM.SelectedValue +
                                                                                " \r");
            WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute("   " + StringLookup.GetLocalizedString("Label_InitialGuess") +
                                                                                InitialGuessOpticalPropertyVM + " \r");

            var inverseResult = SolveInverse();

            ResultOpticalPropertyVM.SetOpticalProperties(inverseResult.FitOpticalProperties.First());
            // todo: this only works for one set of properties

            //Report the results
            if (
                SolutionDomainTypeOptionVM.IndependentVariableAxisOptionVM.SelectedValues.Contains(
                    IndependentVariableAxis.Wavelength) &&
                inverseResult.FitOpticalProperties.Length > 1)
            // If multi-valued OPs, the results aren't in the "scalar" VMs, need to parse OPs directly
            {
                var fitOPs       = inverseResult.FitOpticalProperties;
                var measuredOPs  = inverseResult.MeasuredOpticalProperties;
                var wavelengths  = GetParameterValues(IndependentVariableAxis.Wavelength);
                var wvUnitString = IndependentVariableAxisUnits.NM.GetInternationalizedString();
                var opUnitString = IndependentVariableAxisUnits.InverseMM.GetInternationalizedString();
                var sb           =
                    new StringBuilder("\t[" + StringLookup.GetLocalizedString("Label_Wavelength") + " (" + wvUnitString +
                                      ")]\t\t\t\t\t\t[" + StringLookup.GetLocalizedString("Label_Exact") + "]\t\t\t\t\t\t[" +
                                      StringLookup.GetLocalizedString("Label_ConvergedValues") + "]\t\t\t\t\t\t[" + StringLookup.GetLocalizedString("Label_Units") + "]\r");
                for (var i = 0; i < fitOPs.Length; i++)
                {
                    sb.Append("\t" + wavelengths[i] + "\t\t\t\t\t\t" + measuredOPs[i] + "\t\t\t" + fitOPs[i] + "\t\t\t" +
                              opUnitString + " \r");
                }
                WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute(sb.ToString());
            }
            else
            {
                WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute("   " + StringLookup.GetLocalizedString("Label_Exact") + ": " +
                                                                                    MeasuredOpticalPropertyVM + " \r");
                WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute("   " + StringLookup.GetLocalizedString("Label_ConvergedValues") + ": " +
                                                                                    ResultOpticalPropertyVM + " \r");
                //Display Percent Error
                double muaError  = 0.0;
                double muspError = 0.0;
                if (MeasuredOpticalPropertyVM.Mua > 0)
                {
                    int tempMuaError = (int)(10000.0 * Math.Abs(ResultOpticalPropertyVM.Mua - MeasuredOpticalPropertyVM.Mua) / MeasuredOpticalPropertyVM.Mua);
                    muaError = tempMuaError / 100.0;
                }
                if (MeasuredOpticalPropertyVM.Musp > 0)
                {
                    int tempMuspError = (int)(10000.0 * Math.Abs(ResultOpticalPropertyVM.Musp - MeasuredOpticalPropertyVM.Musp) / MeasuredOpticalPropertyVM.Musp);
                    muspError = tempMuspError / 100.0;
                }
                WindowViewModel.Current.TextOutputVM.TextOutput_PostMessage.Execute("   " + StringLookup.GetLocalizedString("Label_PercentError") +
                                                                                    StringLookup.GetLocalizedString("Label_MuA") + " = " + muaError +
                                                                                    "%  " + StringLookup.GetLocalizedString("Label_MuSPrime") + " = " + muspError + "% \r");
            }

            var axesLabels = GetPlotLabels();

            WindowViewModel.Current.PlotVM.SetAxesLabels.Execute(axesLabels);
            var plotLabels = GetLegendLabels(PlotDataType.Calculated);
            var plotData   = inverseResult.FitDataPoints.Zip(plotLabels, (p, el) => new PlotData(p, el)).ToArray();

            WindowViewModel.Current.PlotVM.PlotValues.Execute(plotData);
        }