Skip to content

Problems

BaseProblem

Base class for defining and solving problems.

Attributes:

Name Type Description
pars dict

Dictionary containing parameters for the problem.

domain Domain

The domain over which the problem is defined.

subproblems dict

Dictionary containing subproblems of the main problem.

postprocessor PostProcessor

Post-processor for analyzing simulation results.

exporter Exporter

Exporter for saving simulation results.

stepper ProportionalTimeStepper

Time stepper for time integration during simulation.

end_checker EndChecker

End checker to checker if the simulation must end.

Source code in src/fragma/problems.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class BaseProblem:
    """
    Base class for defining and solving problems.

    Attributes
    ----------
    pars : dict
        Dictionary containing parameters for the problem.
    domain : Domain
        The domain over which the problem is defined.
    subproblems : dict
        Dictionary containing subproblems of the main problem.
    postprocessor : PostProcessor
        Post-processor for analyzing simulation results.
    exporter : Exporter
        Exporter for saving simulation results.
    stepper : ProportionalTimeStepper
        Time stepper for time integration during simulation.
    end_checker : EndChecker
        End checker to checker if the simulation must end.
    """

    def __init__(self, pars):
        """
        Initialize the BaseProblem.

        Parameters
        ----------
        pars : dict
            Dictionary containing parameters for the problem.
        """
        ### Parameters
        print("\n████ PARAMETERS")
        # Store paramters
        self.pars = pars
        # Display a summary
        print(json.dumps(self.pars, indent=4))
        # Define the domain
        self.domain = Domain(pars["mesh"], pars["model"]["dim"])
        # Define the state variables
        self.define_state_variables()
        # Define the model
        self.define_model(self.domain)
        # Define subproblems
        self.subproblems = {}
        self.define_subproblems()
        # Check if path-following is used
        self.use_path_following = (
            pars.get("loading", {}).get("constraint", None) is not None
        )
        # Initialize post-processing
        postprocess_pars = pars.get("postprocess", {})
        self.postprocessor = PostProcessor(
            self.domain, self.model, self.state, postprocess_pars
        )
        # Initialize the exporter
        functions_to_export = [
            f for f in self.state.values() if f.name != "PreviousCrackPhase"
        ]
        functions_to_export += list(self.postprocessor.funcs.values())
        scalar_data = self.postprocessor.scalar_data
        probes = self.postprocessor.probes
        self.exporter = Exporter(
            self.domain.mesh, functions_to_export, scalar_data, probes
        )
        # Initialize the time stepper
        self.stepper = ProportionalTimeStepper()
        # Initialize the end checker
        end_pars = self.pars["end"]
        self.end_checker = choose_end_checker(
            end_pars, self.stepper, self.postprocessor
        )

    def define_state_variables(self):
        """
        Define the state variables for the problem.

        This method must be implemented in the child class.
        """
        raise NotImplementedError(
            "Solver: The method 'define_state_variables' must be implemented in the child class."
        )

    def define_model(self, domain):
        """
        Define the model for the problem.

        This method must be implemented in the child class.
        """
        raise NotImplementedError(
            "Solver: The method 'define_model' must be implemented in the child class."
        )

    def define_subproblems(self):
        """
        Define the subproblems for the problem.

        This method must be implemented in the child class.
        """
        raise NotImplementedError(
            "Solver: The method 'define_subproblems' must be implemented in the child class."
        )

    def update_subproblems(self, t: float):
        """
        Update the subproblems for the current time step.

        Parameters
        ----------
        t : float
            Current time.
        """
        for subproblem in self.subproblems.values():
            subproblem.update(t)

    def solve(self):
        """
        Solve the problem over time.
        """
        print("\n████ RESOLUTION")
        while not self.end_checker.end():
            # Get time
            t = self.stepper.t
            # Display information
            print(f"\n== Time {t:.8g}")
            # Update subproblems
            self.update_subproblems(t)
            # Solve the problems for this iteration
            self.solve_iteration()
            # Apply post processing
            self.postprocessor.postprocess()
            # Export the results
            self.exporter.export(t)
            # Increment the time stepper
            self.stepper.increment()
        # End export
        self.exporter.end()

    def solve_iteration(self):
        """
        Solve a single iteration of the problem.

        This method must be implemented in the child class.
        """
        raise NotImplementedError(
            "Solver: The method 'solve_iteration' must be implemented in the child class."
        )

__init__(pars)

Initialize the BaseProblem.

Parameters:

Name Type Description Default
pars dict

Dictionary containing parameters for the problem.

required
Source code in src/fragma/problems.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(self, pars):
    """
    Initialize the BaseProblem.

    Parameters
    ----------
    pars : dict
        Dictionary containing parameters for the problem.
    """
    ### Parameters
    print("\n████ PARAMETERS")
    # Store paramters
    self.pars = pars
    # Display a summary
    print(json.dumps(self.pars, indent=4))
    # Define the domain
    self.domain = Domain(pars["mesh"], pars["model"]["dim"])
    # Define the state variables
    self.define_state_variables()
    # Define the model
    self.define_model(self.domain)
    # Define subproblems
    self.subproblems = {}
    self.define_subproblems()
    # Check if path-following is used
    self.use_path_following = (
        pars.get("loading", {}).get("constraint", None) is not None
    )
    # Initialize post-processing
    postprocess_pars = pars.get("postprocess", {})
    self.postprocessor = PostProcessor(
        self.domain, self.model, self.state, postprocess_pars
    )
    # Initialize the exporter
    functions_to_export = [
        f for f in self.state.values() if f.name != "PreviousCrackPhase"
    ]
    functions_to_export += list(self.postprocessor.funcs.values())
    scalar_data = self.postprocessor.scalar_data
    probes = self.postprocessor.probes
    self.exporter = Exporter(
        self.domain.mesh, functions_to_export, scalar_data, probes
    )
    # Initialize the time stepper
    self.stepper = ProportionalTimeStepper()
    # Initialize the end checker
    end_pars = self.pars["end"]
    self.end_checker = choose_end_checker(
        end_pars, self.stepper, self.postprocessor
    )

define_model(domain)

Define the model for the problem.

This method must be implemented in the child class.

Source code in src/fragma/problems.py
100
101
102
103
104
105
106
107
108
def define_model(self, domain):
    """
    Define the model for the problem.

    This method must be implemented in the child class.
    """
    raise NotImplementedError(
        "Solver: The method 'define_model' must be implemented in the child class."
    )

define_state_variables()

Define the state variables for the problem.

This method must be implemented in the child class.

Source code in src/fragma/problems.py
90
91
92
93
94
95
96
97
98
def define_state_variables(self):
    """
    Define the state variables for the problem.

    This method must be implemented in the child class.
    """
    raise NotImplementedError(
        "Solver: The method 'define_state_variables' must be implemented in the child class."
    )

define_subproblems()

Define the subproblems for the problem.

This method must be implemented in the child class.

Source code in src/fragma/problems.py
110
111
112
113
114
115
116
117
118
def define_subproblems(self):
    """
    Define the subproblems for the problem.

    This method must be implemented in the child class.
    """
    raise NotImplementedError(
        "Solver: The method 'define_subproblems' must be implemented in the child class."
    )

solve()

Solve the problem over time.

Source code in src/fragma/problems.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def solve(self):
    """
    Solve the problem over time.
    """
    print("\n████ RESOLUTION")
    while not self.end_checker.end():
        # Get time
        t = self.stepper.t
        # Display information
        print(f"\n== Time {t:.8g}")
        # Update subproblems
        self.update_subproblems(t)
        # Solve the problems for this iteration
        self.solve_iteration()
        # Apply post processing
        self.postprocessor.postprocess()
        # Export the results
        self.exporter.export(t)
        # Increment the time stepper
        self.stepper.increment()
    # End export
    self.exporter.end()

solve_iteration()

Solve a single iteration of the problem.

This method must be implemented in the child class.

Source code in src/fragma/problems.py
155
156
157
158
159
160
161
162
163
def solve_iteration(self):
    """
    Solve a single iteration of the problem.

    This method must be implemented in the child class.
    """
    raise NotImplementedError(
        "Solver: The method 'solve_iteration' must be implemented in the child class."
    )

update_subproblems(t)

Update the subproblems for the current time step.

Parameters:

Name Type Description Default
t float

Current time.

required
Source code in src/fragma/problems.py
120
121
122
123
124
125
126
127
128
129
130
def update_subproblems(self, t: float):
    """
    Update the subproblems for the current time step.

    Parameters
    ----------
    t : float
        Current time.
    """
    for subproblem in self.subproblems.values():
        subproblem.update(t)

ElasticityProblem

Bases: BaseProblem

Solver for 2D elasticity problem (in plane strain or plain stress). The loading are proportional to time.

Attributes:

Name Type Description
model ElasticModel

The elasticity model used for solving the problem.

V_u FunctionSpace

The function space for the displacement field.

state dict

Dictionary containing the state variables.

Source code in src/fragma/problems.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
class ElasticityProblem(BaseProblem):
    """
    Solver for 2D elasticity problem (in plane strain or plain stress).
    The loading are proportional to time.

    Attributes
    ----------
    model : ElasticModel
        The elasticity model used for solving the problem.
    V_u : dolfinx.FunctionSpace
        The function space for the displacement field.
    state : dict
        Dictionary containing the state variables.
    """

    def define_state_variables(self):
        """
        Define the state variables for the problem.
        """
        ### Variational formulation
        print("\n████ DEFINITION OF THE STATE VARIABLES")
        # Define the elements
        # Define finite element spaces
        shape = (self.domain.mesh.geometry.dim,)
        self.V_u = fem.functionspace(self.domain.mesh, ("Lagrange", 1, shape))
        # Define the state variables
        u = fem.Function(self.V_u, name="Displacement")
        # Define the state vector
        self.state = {"u": u}

    def define_model(self, domain):
        """
        Define the model for the problem.

        Parameters
        ----------
        domain : fragma.Domain.domain
            Domain object used to initialize heterogeneous properties.
        """
        # Create the elasticity model
        self.model = ElasticModel(self.pars, domain)

    def define_subproblems(self):
        """
        Define the subproblems for the elasticity problem.
        """
        print("\n████ DEFINITION OF THE SUB-PROBLEMS")
        # Define the displacement problem
        self.subproblems["u"] = create_displacement_subproblem(
            self.pars, self.domain, self.state, self.model
        )

    def solve_iteration(self):
        """
        Solve a single iteration of the elasticity problem.
        """
        # Solve the displacement problem
        self.subproblems["u"].solve()

define_model(domain)

Define the model for the problem.

Parameters:

Name Type Description Default
domain domain

Domain object used to initialize heterogeneous properties.

required
Source code in src/fragma/problems.py
196
197
198
199
200
201
202
203
204
205
206
def define_model(self, domain):
    """
    Define the model for the problem.

    Parameters
    ----------
    domain : fragma.Domain.domain
        Domain object used to initialize heterogeneous properties.
    """
    # Create the elasticity model
    self.model = ElasticModel(self.pars, domain)

define_state_variables()

Define the state variables for the problem.

Source code in src/fragma/problems.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
def define_state_variables(self):
    """
    Define the state variables for the problem.
    """
    ### Variational formulation
    print("\n████ DEFINITION OF THE STATE VARIABLES")
    # Define the elements
    # Define finite element spaces
    shape = (self.domain.mesh.geometry.dim,)
    self.V_u = fem.functionspace(self.domain.mesh, ("Lagrange", 1, shape))
    # Define the state variables
    u = fem.Function(self.V_u, name="Displacement")
    # Define the state vector
    self.state = {"u": u}

define_subproblems()

Define the subproblems for the elasticity problem.

Source code in src/fragma/problems.py
208
209
210
211
212
213
214
215
216
def define_subproblems(self):
    """
    Define the subproblems for the elasticity problem.
    """
    print("\n████ DEFINITION OF THE SUB-PROBLEMS")
    # Define the displacement problem
    self.subproblems["u"] = create_displacement_subproblem(
        self.pars, self.domain, self.state, self.model
    )

solve_iteration()

Solve a single iteration of the elasticity problem.

Source code in src/fragma/problems.py
218
219
220
221
222
223
def solve_iteration(self):
    """
    Solve a single iteration of the elasticity problem.
    """
    # Solve the displacement problem
    self.subproblems["u"].solve()

FractureProblem

Bases: BaseProblem

Solver for fracture problems using a phase-field model.

This class inherits from BaseProblem and provides functionality to solve fracture problems using a phase-field model. It defines state variables, subproblems, and methods to solve the problem over time.

Attributes:

Name Type Description
model FractureModel

The fracture model used for solving the problem.

V_u FunctionSpace

Function space for the displacement field.

V_alpha FunctionSpace

Function space for the fracture phase field.

state dict

Dictionary containing the state variables.

subproblems dict

Dictionary containing subproblems of the main problem.

Source code in src/fragma/problems.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
class FractureProblem(BaseProblem):
    """
    Solver for fracture problems using a phase-field model.

    This class inherits from BaseProblem and provides functionality to solve
    fracture problems using a phase-field model. It defines state variables,
    subproblems, and methods to solve the problem over time.

    Attributes
    ----------
    model : FractureModel
        The fracture model used for solving the problem.
    V_u : dolfinx.FunctionSpace
        Function space for the displacement field.
    V_alpha : dolfinx.FunctionSpace
        Function space for the fracture phase field.
    state : dict
        Dictionary containing the state variables.
    subproblems : dict
        Dictionary containing subproblems of the main problem.
    """

    def define_state_variables(self):
        """
        Define the state variables for the fracture problem.

        This method defines the displacement and fracture phase field variables.
        """
        ### Variational formulation
        print("\n████ DEFINITION OF THE STATE VARIABLES")
        # Define the displacement field
        shape = (self.domain.mesh.geometry.dim,)
        self.V_u = fem.functionspace(self.domain.mesh, ("Lagrange", 1, shape))
        u = fem.Function(self.V_u, name="Displacement")
        # Define the fracture phase field
        self.V_alpha = fem.functionspace(self.domain.mesh, ("Lagrange", 1))
        alpha = fem.Function(self.V_alpha, name="CrackPhase")
        alpha0 = alpha.copy()
        alpha0.name = "PreviousCrackPhase"
        self.state = {"u": u, "alpha": alpha, "alpha0": alpha0}

    def define_model(self, domain):
        """
        Define the model for the problem.

        Parameters
        ----------
        domain : fragma.Domain.domain
            Domain object used to initialize heterogeneous properties.
        """
        # Create the fracture model
        self.model = FractureModel(self.pars, domain)

    def define_subproblems(self):
        """
        Define the subproblems for the fracture problem.

        This method defines the displacement and fracture phase subproblems.
        """
        # Define the displacement problem
        self.subproblems["u"] = create_displacement_subproblem(
            self.pars, self.domain, self.state, self.model
        )
        # Define the displacement problem
        self.subproblems["alpha"] = CrackPhaseSubProblem(
            self.pars, self.domain, self.state, self.model
        )

    def monitor(self, k, error_u, error_a, time_u, time_alpha):
        """
        Monitor the progress of the fracture problem solver.

        This method prints information about the current iteration, including
        the iteration number, error, and computation times for solving the
        displacement and fracture phase subproblems.

        Parameters
        ----------
        k : int
            Alternate minimization iteration number.
        error_u : float
            Displacement error between two successive alternate minimization iterations.
        error_a : float
            Crack phase error between two successive alternate minimization iterations.
        time_u : float
            Computation time for solving the displacement subproblem.
        time_alpha : float
            Computation time for solving the fracture phase subproblem.
        """
        if MPI.COMM_WORLD.rank == 0:
            self.subproblems["u"].l
            print(f"Iter: {k:04d}", end=", ")
            print(f"Error u: {error_u:0.4e}", end=", ")
            print(f"Error a: {error_a:0.4e}", end=", ")
            print(f"Time u: {time_u:0.4e}s", end=", ")
            print(f"Time alpha: {time_alpha:0.4e}s", end=", ")
            print(f"Load factor: {self.subproblems['u'].l:0.4e}")

    def solve_iteration(self):
        """
        Solve a single iteration of the fracture problem.

        This method iteratively solves the displacement and fracture phase
        subproblems until convergence is achieved or the maximum number of
        iterations is reached.
        """
        # Get the state
        u, alpha = self.state["u"], self.state["alpha"]
        # Define state at previous iteration for error computation
        u_old, alpha_old = u.copy(), alpha.copy()
        self.state["alpha0"].x.array[:] = alpha_old.x.array
        # Initialize the errors
        error_u, error_a = 0, 0
        # Get previous displacement (for over-relaxation)
        relaxation = "omega" in self.pars["numerical"]
        if relaxation:
            omega = self.pars["numerical"]["omega"]
        # Perform the alternate minimization
        for k in range(self.pars["numerical"]["max_iter"]):
            # Solve the displacement problem
            time_u_start = time.perf_counter()
            self.subproblems["u"].solve()
            time_u = time.perf_counter() - time_u_start
            # Perform displacement relaxiation
            if relaxation:
                u.x.array[:] = u_old.x.array + omega * (u.x.array[:] - u_old.x.array[:])
                u.vector.assemble()
            # Solve the crack phase problem
            time_alpha_start = time.perf_counter()
            self.subproblems["alpha"].solve()
            time_alpha = time.perf_counter() - time_alpha_start
            # Perform crack phase relaxation
            if relaxation:
                dalpha = alpha.vector[:] - alpha_old.vector[:]
                omega_bar = omega * np.ones((len(dalpha),))
                new_alpha = alpha_old.vector[:] + omega_bar * dalpha
                # Add a counter
                while new_alpha.max() > 1.0:
                    omega_bar = 1 / 2 * (1 + omega_bar)
                    new_alpha = alpha_old.vector[:] + omega_bar * dalpha
                alpha.vector[:] = alpha_old.vector[:] + omega_bar * dalpha
                alpha.vector.assemble()
                print(f"Crack phase relaxation: f{omega_bar[0]}")
            # Check errors (L2)
            error_u = np.max(np.abs(u.x.array - u_old.x.array))
            error_a = np.max(np.abs(alpha.x.array - alpha_old.x.array))
            # Check convergence
            converged = (
                error_u <= self.pars["numerical"]["utol"]
                and error_a <= self.pars["numerical"]["atol"]
            )
            # Display information
            self.monitor(k, error_u, error_a, time_u, time_alpha)
            # Update old fields
            u_old.x.array[:] = u.x.array
            u_old.x.scatter_forward()
            alpha_old.x.array[:] = alpha.x.array
            alpha_old.x.scatter_forward()
            # Stop to iterate if the calculation is converged
            if converged:
                break
        else:
            raise RuntimeError(
                f"Could not converge after {k:3d} iteration, error_u {error_u:3.4e}, error_a {error_a:3.4e}"
            )

define_model(domain)

Define the model for the problem.

Parameters:

Name Type Description Default
domain domain

Domain object used to initialize heterogeneous properties.

required
Source code in src/fragma/problems.py
267
268
269
270
271
272
273
274
275
276
277
def define_model(self, domain):
    """
    Define the model for the problem.

    Parameters
    ----------
    domain : fragma.Domain.domain
        Domain object used to initialize heterogeneous properties.
    """
    # Create the fracture model
    self.model = FractureModel(self.pars, domain)

define_state_variables()

Define the state variables for the fracture problem.

This method defines the displacement and fracture phase field variables.

Source code in src/fragma/problems.py
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
def define_state_variables(self):
    """
    Define the state variables for the fracture problem.

    This method defines the displacement and fracture phase field variables.
    """
    ### Variational formulation
    print("\n████ DEFINITION OF THE STATE VARIABLES")
    # Define the displacement field
    shape = (self.domain.mesh.geometry.dim,)
    self.V_u = fem.functionspace(self.domain.mesh, ("Lagrange", 1, shape))
    u = fem.Function(self.V_u, name="Displacement")
    # Define the fracture phase field
    self.V_alpha = fem.functionspace(self.domain.mesh, ("Lagrange", 1))
    alpha = fem.Function(self.V_alpha, name="CrackPhase")
    alpha0 = alpha.copy()
    alpha0.name = "PreviousCrackPhase"
    self.state = {"u": u, "alpha": alpha, "alpha0": alpha0}

define_subproblems()

Define the subproblems for the fracture problem.

This method defines the displacement and fracture phase subproblems.

Source code in src/fragma/problems.py
279
280
281
282
283
284
285
286
287
288
289
290
291
292
def define_subproblems(self):
    """
    Define the subproblems for the fracture problem.

    This method defines the displacement and fracture phase subproblems.
    """
    # Define the displacement problem
    self.subproblems["u"] = create_displacement_subproblem(
        self.pars, self.domain, self.state, self.model
    )
    # Define the displacement problem
    self.subproblems["alpha"] = CrackPhaseSubProblem(
        self.pars, self.domain, self.state, self.model
    )

monitor(k, error_u, error_a, time_u, time_alpha)

Monitor the progress of the fracture problem solver.

This method prints information about the current iteration, including the iteration number, error, and computation times for solving the displacement and fracture phase subproblems.

Parameters:

Name Type Description Default
k int

Alternate minimization iteration number.

required
error_u float

Displacement error between two successive alternate minimization iterations.

required
error_a float

Crack phase error between two successive alternate minimization iterations.

required
time_u float

Computation time for solving the displacement subproblem.

required
time_alpha float

Computation time for solving the fracture phase subproblem.

required
Source code in src/fragma/problems.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
def monitor(self, k, error_u, error_a, time_u, time_alpha):
    """
    Monitor the progress of the fracture problem solver.

    This method prints information about the current iteration, including
    the iteration number, error, and computation times for solving the
    displacement and fracture phase subproblems.

    Parameters
    ----------
    k : int
        Alternate minimization iteration number.
    error_u : float
        Displacement error between two successive alternate minimization iterations.
    error_a : float
        Crack phase error between two successive alternate minimization iterations.
    time_u : float
        Computation time for solving the displacement subproblem.
    time_alpha : float
        Computation time for solving the fracture phase subproblem.
    """
    if MPI.COMM_WORLD.rank == 0:
        self.subproblems["u"].l
        print(f"Iter: {k:04d}", end=", ")
        print(f"Error u: {error_u:0.4e}", end=", ")
        print(f"Error a: {error_a:0.4e}", end=", ")
        print(f"Time u: {time_u:0.4e}s", end=", ")
        print(f"Time alpha: {time_alpha:0.4e}s", end=", ")
        print(f"Load factor: {self.subproblems['u'].l:0.4e}")

solve_iteration()

Solve a single iteration of the fracture problem.

This method iteratively solves the displacement and fracture phase subproblems until convergence is achieved or the maximum number of iterations is reached.

Source code in src/fragma/problems.py
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
def solve_iteration(self):
    """
    Solve a single iteration of the fracture problem.

    This method iteratively solves the displacement and fracture phase
    subproblems until convergence is achieved or the maximum number of
    iterations is reached.
    """
    # Get the state
    u, alpha = self.state["u"], self.state["alpha"]
    # Define state at previous iteration for error computation
    u_old, alpha_old = u.copy(), alpha.copy()
    self.state["alpha0"].x.array[:] = alpha_old.x.array
    # Initialize the errors
    error_u, error_a = 0, 0
    # Get previous displacement (for over-relaxation)
    relaxation = "omega" in self.pars["numerical"]
    if relaxation:
        omega = self.pars["numerical"]["omega"]
    # Perform the alternate minimization
    for k in range(self.pars["numerical"]["max_iter"]):
        # Solve the displacement problem
        time_u_start = time.perf_counter()
        self.subproblems["u"].solve()
        time_u = time.perf_counter() - time_u_start
        # Perform displacement relaxiation
        if relaxation:
            u.x.array[:] = u_old.x.array + omega * (u.x.array[:] - u_old.x.array[:])
            u.vector.assemble()
        # Solve the crack phase problem
        time_alpha_start = time.perf_counter()
        self.subproblems["alpha"].solve()
        time_alpha = time.perf_counter() - time_alpha_start
        # Perform crack phase relaxation
        if relaxation:
            dalpha = alpha.vector[:] - alpha_old.vector[:]
            omega_bar = omega * np.ones((len(dalpha),))
            new_alpha = alpha_old.vector[:] + omega_bar * dalpha
            # Add a counter
            while new_alpha.max() > 1.0:
                omega_bar = 1 / 2 * (1 + omega_bar)
                new_alpha = alpha_old.vector[:] + omega_bar * dalpha
            alpha.vector[:] = alpha_old.vector[:] + omega_bar * dalpha
            alpha.vector.assemble()
            print(f"Crack phase relaxation: f{omega_bar[0]}")
        # Check errors (L2)
        error_u = np.max(np.abs(u.x.array - u_old.x.array))
        error_a = np.max(np.abs(alpha.x.array - alpha_old.x.array))
        # Check convergence
        converged = (
            error_u <= self.pars["numerical"]["utol"]
            and error_a <= self.pars["numerical"]["atol"]
        )
        # Display information
        self.monitor(k, error_u, error_a, time_u, time_alpha)
        # Update old fields
        u_old.x.array[:] = u.x.array
        u_old.x.scatter_forward()
        alpha_old.x.array[:] = alpha.x.array
        alpha_old.x.scatter_forward()
        # Stop to iterate if the calculation is converged
        if converged:
            break
    else:
        raise RuntimeError(
            f"Could not converge after {k:3d} iteration, error_u {error_u:3.4e}, error_a {error_a:3.4e}"
        )