|
24 | 24 | # The full text of the license can be found in the file LICENSE.md at |
25 | 25 | # the top level directory of EdelweissMPM. |
26 | 26 | # --------------------------------------------------------------------- |
27 | | - |
| 27 | +from collections.abc import Callable |
28 | 28 |
|
29 | 29 | import edelweissfe.utils.performancetiming as performancetiming |
| 30 | +import numpy as np |
30 | 31 | from edelweissfe.constraints.base.constraintbase import ConstraintBase |
31 | 32 | from edelweissfe.journal.journal import Journal |
32 | 33 | from edelweissfe.numerics.dofmanager import DofManager, DofVector |
@@ -88,6 +89,10 @@ class NonlinearQuasistaticSolver(NonlinearImplicitSolverBase): |
88 | 89 | "spec. absolute field correction tolerances": dict(), |
89 | 90 | "failed increment cutback factor": 0.25, |
90 | 91 | "fall back to quasi Newton after allowed residual growths": False, |
| 92 | + "line search": False, |
| 93 | + "line search after n iterations": 5, |
| 94 | + "line search every n iterations": 2, |
| 95 | + "line search alphas": [0.8, 1.0, 1.2], |
91 | 96 | } |
92 | 97 |
|
93 | 98 | def __init__(self, journal: Journal): |
@@ -490,41 +495,30 @@ def _newtonSolve( |
490 | 495 | quasi_Newton = False |
491 | 496 |
|
492 | 497 | while True: |
493 | | - PInt[:] = K_VIJ[:] = F[:] = PExt[:] = 0.0 |
494 | | - |
495 | | - self._prepareMaterialPoints(materialPoints, timeStep.totalTime, timeStep.timeIncrement) |
496 | | - self._interpolateFieldsToMaterialPoints(activeCells, dU) |
497 | | - self._interpolateFieldsToMaterialPoints(cellElements, dU) |
498 | | - self._computeMaterialPoints(materialPoints, timeStep.totalTime, timeStep.timeIncrement) |
499 | | - |
500 | | - self._computeCells( |
501 | | - activeCells, dU, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
502 | | - ) |
503 | | - |
504 | | - self._computeElements( |
505 | | - elements, dU, Un, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
506 | | - ) |
507 | | - |
508 | | - self._computeCellElements( |
509 | | - cellElements, dU, Un, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
510 | | - ) |
511 | 498 |
|
512 | | - self._computeParticles( |
513 | | - particles, dU, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
| 499 | + Rhs, K_VIJ, F, PInt, PExt = self._computeSystem( |
| 500 | + dirichlets, |
| 501 | + bodyLoads, |
| 502 | + distributedLoads, |
| 503 | + particleDistributedLoads, |
| 504 | + reducedNodeSets, |
| 505 | + elements, |
| 506 | + Un, |
| 507 | + activeCells, |
| 508 | + cellElements, |
| 509 | + materialPoints, |
| 510 | + particles, |
| 511 | + constraints, |
| 512 | + timeStep, |
| 513 | + theDofManager, |
| 514 | + dU, |
| 515 | + Rhs, |
| 516 | + K_VIJ, |
| 517 | + PInt, |
| 518 | + PExt, |
| 519 | + F, |
514 | 520 | ) |
515 | 521 |
|
516 | | - self._computeConstraints(constraints, dU, PInt, K_VIJ, timeStep) |
517 | | - |
518 | | - PExt, K = self._computeBodyLoads(bodyLoads, PExt, K_VIJ, timeStep, theDofManager, activeCells) |
519 | | - PExt, K = self._computeCellDistributedLoads(distributedLoads, PExt, K_VIJ, timeStep, theDofManager) |
520 | | - |
521 | | - PExt, K = self._computeParticleDistributedLoads( |
522 | | - particleDistributedLoads, PExt, K_VIJ, timeStep, theDofManager |
523 | | - ) |
524 | | - |
525 | | - Rhs[:] = -PInt |
526 | | - Rhs -= PExt |
527 | | - |
528 | 522 | if iterationCounter == 0 and dirichlets: |
529 | 523 | Rhs = self._applyDirichlet(timeStep, Rhs, dirichlets, reducedNodeSets, theDofManager) |
530 | 524 | else: |
@@ -587,9 +581,177 @@ def _newtonSolve( |
587 | 581 | K_CSR = self._applyDirichletKCsr(K_CSR, dirichlets, theDofManager, reducedNodeSets) |
588 | 582 |
|
589 | 583 | ddU = self._linearSolve(K_CSR, Rhs, linearSolver) |
| 584 | + |
| 585 | + if ( |
| 586 | + iterationOptions["line search"] |
| 587 | + and iterationCounter > iterationOptions["line search after n iterations"] |
| 588 | + and iterationCounter % iterationOptions["line search every n iterations"] == 0 |
| 589 | + ): |
| 590 | + |
| 591 | + alphas = iterationOptions["line search alphas"] |
| 592 | + |
| 593 | + def _computeResidualCallback(ddU_linesearch: DofVector) -> DofVector: |
| 594 | + |
| 595 | + dU_linesearch = dU.copy() |
| 596 | + dU_linesearch += ddU_linesearch |
| 597 | + |
| 598 | + Rhs_, *rest = self._computeSystem( |
| 599 | + dirichlets, |
| 600 | + bodyLoads, |
| 601 | + distributedLoads, |
| 602 | + particleDistributedLoads, |
| 603 | + reducedNodeSets, |
| 604 | + elements, |
| 605 | + Un, |
| 606 | + activeCells, |
| 607 | + cellElements, |
| 608 | + materialPoints, |
| 609 | + particles, |
| 610 | + constraints, |
| 611 | + timeStep, |
| 612 | + theDofManager, |
| 613 | + dU_linesearch, |
| 614 | + Rhs, |
| 615 | + K_VIJ, |
| 616 | + PInt, |
| 617 | + PExt, |
| 618 | + F, |
| 619 | + ) |
| 620 | + for dirichlet in dirichlets: |
| 621 | + Rhs_[self._findDirichletIndices(theDofManager, dirichlet, reducedNodeSets[dirichlet.nSet])] = ( |
| 622 | + 0.0 |
| 623 | + ) |
| 624 | + |
| 625 | + return Rhs_ |
| 626 | + |
| 627 | + ddU = self._quadraticLineSearch(_computeResidualCallback, ddU, alphas) |
| 628 | + |
| 629 | + else: |
| 630 | + if initialGuess is not None: |
| 631 | + # We only do one iteration with the initial guess, then we switch to standard Newton |
| 632 | + quasi_Newton = False |
| 633 | + |
590 | 634 | dU += ddU |
591 | 635 | iterationCounter += 1 |
592 | 636 |
|
593 | 637 | iterationHistory = {"iterations": iterationCounter, "incrementResidualHistory": incrementResidualHistory} |
594 | 638 |
|
595 | 639 | return dU, PInt, iterationHistory, newtonCache |
| 640 | + |
| 641 | + @performancetiming.timeit("compute system") |
| 642 | + def _computeSystem( |
| 643 | + self, |
| 644 | + dirichlets: list[DirichletBase], |
| 645 | + bodyLoads: list, |
| 646 | + distributedLoads: list, |
| 647 | + particleDistributedLoads: list, |
| 648 | + reducedNodeSets: list, |
| 649 | + elements: list, |
| 650 | + Un: DofVector, |
| 651 | + activeCells: list, |
| 652 | + cellElements: list, |
| 653 | + materialPoints: list, |
| 654 | + particles: list, |
| 655 | + constraints: list, |
| 656 | + timeStep: TimeStep, |
| 657 | + theDofManager: DofManager, |
| 658 | + dU, |
| 659 | + Rhs, |
| 660 | + K_VIJ, |
| 661 | + PInt, |
| 662 | + PExt, |
| 663 | + F, |
| 664 | + ): |
| 665 | + """Compute the global residual vector and the global stiffness matrix.""" |
| 666 | + |
| 667 | + PInt[:] = K_VIJ[:] = F[:] = PExt[:] = 0.0 |
| 668 | + |
| 669 | + self._prepareMaterialPoints(materialPoints, timeStep.totalTime, timeStep.timeIncrement) |
| 670 | + self._interpolateFieldsToMaterialPoints(activeCells, dU) |
| 671 | + self._interpolateFieldsToMaterialPoints(cellElements, dU) |
| 672 | + self._computeMaterialPoints(materialPoints, timeStep.totalTime, timeStep.timeIncrement) |
| 673 | + |
| 674 | + self._computeCells(activeCells, dU, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager) |
| 675 | + |
| 676 | + self._computeElements( |
| 677 | + elements, dU, Un, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
| 678 | + ) |
| 679 | + |
| 680 | + self._computeCellElements( |
| 681 | + cellElements, dU, Un, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager |
| 682 | + ) |
| 683 | + |
| 684 | + self._computeParticles(particles, dU, PInt, F, K_VIJ, timeStep.totalTime, timeStep.timeIncrement, theDofManager) |
| 685 | + |
| 686 | + self._computeConstraints(constraints, dU, PInt, K_VIJ, timeStep) |
| 687 | + |
| 688 | + PExt, K = self._computeBodyLoads(bodyLoads, PExt, K_VIJ, timeStep, theDofManager, activeCells) |
| 689 | + PExt, K = self._computeCellDistributedLoads(distributedLoads, PExt, K_VIJ, timeStep, theDofManager) |
| 690 | + |
| 691 | + PExt, K = self._computeParticleDistributedLoads(particleDistributedLoads, PExt, K_VIJ, timeStep, theDofManager) |
| 692 | + |
| 693 | + Rhs[:] = -PInt |
| 694 | + Rhs -= PExt |
| 695 | + |
| 696 | + return Rhs, K, F, PInt, PExt |
| 697 | + |
| 698 | + @performancetiming.timeit("line search") |
| 699 | + def _quadraticLineSearch( |
| 700 | + self, |
| 701 | + computeResidual: Callable[[DofVector], DofVector], |
| 702 | + ddU: DofVector, |
| 703 | + alphas: list, |
| 704 | + ): |
| 705 | + """Perform a quadratic line search to determine an optimal step length. |
| 706 | +
|
| 707 | + Parameters |
| 708 | + ---------- |
| 709 | + computeSystem |
| 710 | + A callback function to compute the system residual for a given solution increment. |
| 711 | + ddU |
| 712 | + The current solution increment vector. |
| 713 | + alphas |
| 714 | + The list of alpha values to be tried. |
| 715 | +
|
| 716 | + Returns |
| 717 | + ------- |
| 718 | + DofVector |
| 719 | + The updated solution increment vector after line search. |
| 720 | + """ |
| 721 | + |
| 722 | + R_trial_values = [] |
| 723 | + ddU_linesearch = ddU.copy() |
| 724 | + |
| 725 | + for alpha in alphas: |
| 726 | + ddU_linesearch[:] = ddU * alpha |
| 727 | + |
| 728 | + Rhs_trial = computeResidual(ddU_linesearch) |
| 729 | + |
| 730 | + R_trial = np.linalg.norm(Rhs_trial, np.inf) |
| 731 | + R_trial_values.append(R_trial) |
| 732 | + self.journal.message( |
| 733 | + " line search try with alpha {:7.4f}, ||R||∞ {:11.9e}".format(alpha, R_trial), |
| 734 | + self.identification, |
| 735 | + level=2, |
| 736 | + ) |
| 737 | + |
| 738 | + coeffs = np.polyfit(alphas, R_trial_values, 2) |
| 739 | + alpha_opt = -coeffs[1] / (2 * coeffs[0]) |
| 740 | + |
| 741 | + alpha = max(0.1, min(1.5, alpha_opt)) |
| 742 | + self.journal.message( |
| 743 | + " line search selected alpha {:7.4f} from quadratic fit".format(alpha), |
| 744 | + self.identification, |
| 745 | + level=2, |
| 746 | + ) |
| 747 | + |
| 748 | + ddU_final = ddU * alpha |
| 749 | + Rhs_final = computeResidual(ddU_final) |
| 750 | + |
| 751 | + self.journal.message( |
| 752 | + " line search final ||R||∞ {:11.9e}".format(np.linalg.norm(Rhs_final, np.inf)), |
| 753 | + self.identification, |
| 754 | + level=2, |
| 755 | + ) |
| 756 | + |
| 757 | + return ddU_final |
0 commit comments