/// <summary>
        /// Returns the crack path after repeatedly executing: XFEM analysis, SIF calculation, crack propagation
        /// </summary>
        /// <returns></returns>
        public void Analyze()
        {
            int analysisStep;

            for (analysisStep = 0; analysisStep < maxIterations; ++analysisStep)
            {
                Debug.WriteLine($"Crack propagation step {analysisStep}");
                Console.WriteLine($"Crack propagation step {analysisStep}");

                // Apply the updated enrichements.
                crack.UpdateEnrichments();

                // Update the mesh partitioning and identify unmodified subdomains to avoid fully processing them again.
                UpdateSubdomains();

                // Order and count dofs
                solver.OrderDofs(false);
                foreach (ILinearSystem linearSystem in linearSystems.Values)
                {
                    if (linearSystem.Subdomain.ConnectivityModified)
                    {
                        linearSystem.Reset(); // Necessary to define the linear system's size
                        linearSystem.Subdomain.Forces = Vector.CreateZero(linearSystem.Size);
                    }
                }

                // Create the stiffness matrix and then the forces vector
                //problem.ClearMatrices();
                BuildMatrices();
                model.AssignLoads(solver.DistributeNodalLoads);
                foreach (ILinearSystem linearSystem in linearSystems.Values)
                {
                    linearSystem.RhsVector = linearSystem.Subdomain.Forces;
                }
                AddEquivalentNodalLoadsToRhs();

                // Plot domain decomposition data, if necessary
                if (DDLogger != null)
                {
                    DDLogger.PlotSubdomains(model);
                }

                // Solve the linear system
                solver.Solve();

                //// Output field data
                //if (fieldOutput != null)
                //{
                //    fieldOutput.WriteOutputData(solver.DofOrderer, freeDisplacements, constrainedDisplacements, iteration);
                //}

                // Let the crack propagate
                //Vector constrainedDisplacements = model.CalculateConstrainedDisplacements(solver.DofOrderer);
                var freeDisplacements = new Dictionary <int, Vector>();
                foreach (int s in linearSystems.Keys)
                {
                    freeDisplacements[s] = (Vector)(linearSystems[s].Solution);                                   //TODO: avoid this cast.
                }
                crack.Propagate(freeDisplacements);

                // Check convergence
                //TODO: Perhaps this should be done by the crack geometry or the Propagator itself and handled via exceptions

                foreach (var tipPropagator in crack.CrackTipPropagators)
                {
                    double sifEffective = CalculateEquivalentSIF(tipPropagator.Value.Logger.SIFsMode1[analysisStep],
                                                                 tipPropagator.Value.Logger.SIFsMode2[analysisStep]);
                    //Console.WriteLine("Keff = " + sifEffective);
                    if (sifEffective >= fractureToughness)
                    {
                        Termination = CrackPropagationTermination.FractureToughnessIsExceeded;
                        return;
                    }
                    if (!model.Boundary.IsInside(tipPropagator.Key))
                    {
                        Termination = CrackPropagationTermination.CrackExitsDomainBoundary;
                        return;
                    }
                }
            }
            Termination = CrackPropagationTermination.RequiredIterationsWereCompleted;
        }