• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

trixi-framework / HOHQMesh / 30318764346

28 Jul 2026 12:55AM UTC coverage: 76.972% (+1.1%) from 75.909%
30318764346

Pull #166

github

web-flow
Merge 3346edfd3 into 955f92dca
Pull Request #166: Add L2/H1 boundary optimization

1631 of 1964 new or added lines in 40 files covered. (83.04%)

48 existing lines in 4 files now uncovered.

9563 of 12424 relevant lines covered (76.97%)

634449.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.7
/Source/BoundaryOptimization/CurveOptimization.f90
1
! MIT License
2
!
3
! Copyright (c) 2010-present David A. Kopriva and other contributors: AUTHORS.md
4
!
5
! Permission is hereby granted, free of charge, to any person obtaining a copy
6
! of this software and associated documentation files (the "Software"), to deal
7
! in the Software without restriction, including without limitation the rights
8
! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
! copies of the Software, and to permit persons to whom the Software is
10
! furnished to do so, subject to the following conditions:
11
!
12
! The above copyright notice and this permission notice shall be included in all
13
! copies or substantial portions of the Software.
14
!
15
! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
! SOFTWARE.
22
!
23
! FTObjectLibrary contains code that, to the best of our knowledge, has been released as
24
! public domain software:
25
! * `b3hs_hash_key_jenkins`: originally by Rich Townsend,
26
! https://groups.google.com/forum/#!topic/comp.lang.fortran/RWoHZFt39ng, 2005
27
!
28
! --- End License
29
!
30
!////////////////////////////////////////////////////////////////////////
31
!
32
!      CurveOptimization.f90
33
!      Created: November 20, 2025 at 11:10 AM
34
!      By: David Kopriva
35
!
36
!  Compute an optimized approximation to a curve.
37
!
38
!  There are three main entry points:
39
!
40
!
41
!  (1) SUBROUTINE OptimizeCurve(curve, polyOrder, cuts, options, newName, newID, optimized)
42
!
43
!      Use this if the location of the segment boundaries given in cuts is known.
44
!      Returns an SMCurve subclass "optimized".
45
!
46
!  (2) SUBROUTINE OptimizeCurveWithSubdivisions(curve, polyOrder, options, newName, newID, optimized)
47
!
48
!      Use this to automatically compute the segments so that the error is is within the desired
49
!      tolerance, toler. Returns an SMCurve subclass "optimized". Uses subdivision algorithm
50
!      *** THIS ROUTINE IS INEFFICIENT COMPARED TO THE NEXT. It IS INCLUDED BUT COMMENTED OUT FOR SOME FUTURE NEED ***
51
!
52
!  (3) SUBROUTINE OptimizeCurveByMarching(curve, polyOrder, options, newName, newID, optimized)
53
!
54
!      Use this to automatically compute the segments so that the error is is within the desired
55
!      tolerance, toler. Returns an SMCurve subclass "optimized". Uses the marching algorithm
56
!
57
!
58
!////////////////////////////////////////////////////////////////////////
59
!
60
   Module CurveOptimization
61
   USE SMCurveClass
62
   USE ConstrainedMultiH1Optimization
63
   USE MultiSegmentModalCurveClass
64
   IMPLICIT NONE
65

66
   INTEGER, PARAMETER, PRIVATE :: MAX_DEPTH = 10
67
   INTEGER, PARAMETER, PRIVATE :: LEFT_SIDE = 0, RIGHT_SIDE = 1
68
   INTEGER, PARAMETER          :: USER_NORM = 1, MAX_NORM   = 2, MAX_NORM_DERIV = 3
69
!
70
!  ========
71
   CONTAINS
72
!  ========
73
!
74
!////////////////////////////////////////////////////////////////////////
75
!
76
   SUBROUTINE OptimizeCurve(curve, polyOrder, cuts, breakIndices, &
514✔
77
                            options, newName, newID, optimized)
78
!
79
!  ----------------------------------------------------
80
!  Given an SMCurve, return and optimal multi-segment
81
!  curve that approximates it
82
!  ----------------------------------------------------
83
!
84
      IMPLICIT NONE
85
!
86
!     ---------
87
!     Arguments
88
!     ---------
89
!
90
      CLASS(SMCurve), POINTER :: curve              ! The curve to be approximated
91
      CLASS(SMCurve), POINTER :: optimized          ! The new approximation to be returned
92
      INTEGER                 :: polyOrder          ! polynomial order of the approximation
93
      REAL(KIND=RP)           :: cuts(0:)           ! Segment boundaries of the new approximation
94
      CHARACTER(LEN=*)        :: newName            ! Name of the new curve
95
      INTEGER                 :: newID              ! ID of the new curve
96
      TYPE(OptimizerOptions)  :: options            ! parameters for the approximation
97
      INTEGER, ALLOCATABLE    :: breakIndices(:)    ! index in optimizedCuts where break boundaries occur
98
!
99
!     ---------------
100
!     Local variables
101
!     ---------------
102
!
103
      TYPE(MultiH1Optimizer)                   :: multiOptimizer
514✔
104
      TYPE(MultiSegmentModalCurve)   , POINTER :: MSMCurve
105

106
      INTEGER                                  :: quadratureOrder
107
      INTEGER                                  :: nSegments
108
      REAL(KIND=RP), ALLOCATABLE               :: optimalCoefficients(:,:,:)
514✔
109
!
110
!     ---------------
111
!     Optimizer setup
112
!     ---------------
113
!
114
      nSegments       = SIZE(cuts) - 1
514✔
115
      quadratureOrder = 2*polyOrder
514✔
116

117
      CALL multiOptimizer % construct(                                 &
118
                                      N            = polyOrder,        &
119
                                      M            = quadratureOrder,  &
120
                                      nSegments    = nSegments,        &
121
                                      cuts         = cuts,             &
122
                                      breakIndices = breakIndices,     &
123
                                      options      = options)
514✔
124
!
125
!     ------------------
126
!     Find optimal curve
127
!     ------------------
128
!
129
      ALLOCATE(optimalCoefficients(0:polyOrder,2,nSegments), source = 0.0_RP)
9,604✔
130
      CALL multiOptimizer % Optimize(curve, optimalCoefficients)
514✔
131
!
132
!     --------------------------------------------------------------
133
!     Create a multiSegmentModalCurve from the computed coefficients
134
!     --------------------------------------------------------------
135
!
136
      ALLOCATE(MSMCurve)
514✔
137
      CALL MSMCurve % constructMultiSegmentModalCurve(curve, cuts, optimalCoefficients, newName, newID)
514✔
138

139
      optimized => MSMCurve
514✔
140

141
   END SUBROUTINE OptimizeCurve
1,028✔
142
!
143
!////////////////////////////////////////////////////////////////////////
144
!
145
   SUBROUTINE OptimizeCurveByMarching(curve, polyOrder, breaks, breakIndices, &
2✔
146
                                      options, newName, newID, optimized)
147
!
148
!  ----------------------------------------------------------------------------------
149
!  March along the curve and use bisection and secant to find the length of each
150
!  segment so that the H^1 error is less than the tolerance allowed for that segment.
151
!  Clean up at the end by applying the global optimization
152
!  ----------------------------------------------------------------------------------
153
!
154
      IMPLICIT NONE
155
!
156
!     ---------
157
!     Arguments
158
!     ---------
159
!
160
      CLASS(SMCurve), POINTER :: curve              ! The curve to be approximated
161
      CLASS(SMCurve), POINTER :: optimized          ! The new approximation
162
      INTEGER                 :: polyOrder          ! polynomial order of the new approximation
163
      REAL(KIND=RP)           :: breaks(0:)         ! Location of forced cut points, starting with t = 0.0
164
                                                    ! If none, use breaks = [0.0_RP,1.0_RP]
165
      CHARACTER(LEN=*)        :: newName            ! Name of the new curve
166
      INTEGER                 :: newID              ! ID of the new curve
167
      TYPE(OptimizerOptions)  :: options            ! parameters for the approximation
168
      INTEGER, ALLOCATABLE    :: breakIndices(:)    ! index in optimizedCuts where break boundaries occur
169
!
170
!     ---------------
171
!     Local variables
172
!     ---------------
173
!
174
      REAL(KIND=RP), ALLOCATABLE             :: optimizedCuts(:)
2✔
175
      CLASS(SMCurve)               , POINTER :: optimizedCurve
176
      CLASS(MultiSegmentModalCurve), POINTER :: msmCurve
177
      TYPE(GaussQuadratureType)              :: gQuad
2✔
178
      TYPE(OptimizerOptions)                 :: savedOptions   ! parameters for the marching algorithm
179
      REAL(KIND=RP), ALLOCATABLE             :: errors(:,:)
2✔
180
      REAL(KIND=RP)                          :: e, errorRatio
181
      INTEGER                                :: n
182

183

184
      CALL ConstructGaussQuadrature(gQuad, 4*polyOrder) ! For error computation. The 4 is arbitrary
2✔
185
!
186
!     -----------------------------------------------------------------------
187
!     Save the options because for marching only end constraints will be used
188
!     -----------------------------------------------------------------------
189
!
190
      savedOptions            = options
2✔
191
      options % endConstraint = FIXED_CONSTRAINT
2✔
192

193
      DO n = 1,3 ! This is a try/try again to account for constraints not being included in marching.
5✔
194
!
195
!        ---------------------------
196
!        Find the optimized segments
197
!        ---------------------------
198
!
199
         CALL FindOptimizedCuts(curve, polyOrder, breaks, options, gQuad, &
200
                                optimizedCuts, breakIndices)
4✔
201
!
202
!        ------------------------------------------------------------
203
!        Construct the global polynomial using the optimized segments
204
!        ------------------------------------------------------------
205
!
206
         CALL OptimizeCurve(curve              = curve,              &
207
                            polyOrder          = polyOrder,          &
208
                            cuts               = optimizedCuts,      &
209
                            breakIndices       = breakIndices,       &
210
                            options            = savedOptions,       &
211
                            newName            = newName,            &
212
                            newID              = newID,              &
213
                            optimized          = optimizedCurve)
4✔
214
!
215
!        --------------------------------------------------------------------------
216
!        See if the error is within tolerance. Since the marching can only use the
217
!        FIXED_CONSTRAINT option, and since errors increase as the constraint
218
!        order increases, then the error won't be within the tolerance. If not,
219
!        create a new tolerance that is small enough so that when done we should
220
!        be OK
221
!        --------------------------------------------------------------------------
222
!
223
         CALL castToMultiSegmentModalCurve(optimizedCurve, msmCurve)
4✔
224
         CALL SegmentErrors(exact          = curve,    &
225
                            segmentedCurve = msmCurve, &
226
                            gQuad          = gQuad,    &
227
                            errors         = errors)
4✔
228
         e = MAXVAL(errors(:,USER_NORM))
43✔
229
         IF ( e .le. options % toler )     EXIT
4✔
230

231
         errorRatio = options % toler/e
3✔
232
         options % toler = options % toler*errorRatio
3✔
233
!         IF(errorRatio < 1.0_RP) WRITE(0,*) "Error required adjusting", e, errorRatio, TRIM(curve % curveName()) !DEBUG
234
!
235
         IF ( n < 3 )     THEN
4✔
236
            DEALLOCATE(errors)
2✔
237
            DEALLOCATE(optimizedCuts)
2✔
238
            CALL releaseMultiSegmentModalCurve(msmCurve)
2✔
239
         END IF
240

241
      END DO
242

243
      optimized => optimizedCurve
2✔
244
!
245
   END SUBROUTINE OptimizeCurveByMarching
4✔
246
!
247
!////////////////////////////////////////////////////////////////////////
248
!
249
   SUBROUTINE FindOptimizedCuts(curve, polyOrder, breaks, options, gQuad, &
6✔
250
                                optimizedCuts, breakIndices)
251
!
252
!  ----------------------------------------------------------------------------------
253
!  March along the curve to find the length of each
254
!  segment so that the H^1 error is less than the tolerance allowed for that segment.
255
!  ----------------------------------------------------------------------------------
256
!
257
      IMPLICIT NONE
258
!
259
!     ---------
260
!     Arguments
261
!     ---------
262
!
263
      CLASS(SMCurve), POINTER   , INTENT(IN)  :: curve              ! The curve to be approximated
264
      INTEGER                   , INTENT(IN)  :: polyOrder          ! polynomial order of the new approximation
265
      REAL(KIND=RP)             , INTENT(IN)  :: breaks(0:)         ! t Location of forced break points, starting with t = 0.0
266
!                                                                     If none, use [0.0_RP,1.0_RP]
267
      TYPE(OptimizerOptions)   ,  INTENT(IN)  :: options            ! parameters for the approximation
268
      TYPE(GaussQuadratureType),  INTENT(IN)  :: gQuad
269
      REAL(KIND=RP), ALLOCATABLE, INTENT(OUT) :: optimizedCuts(:)   ! t location of segment boundaries starting with t = 0
270
      INTEGER      , ALLOCATABLE, INTENT(OUT) :: breakIndices(:)    ! index in optimizedCuts where break boundaries occur
271
!
272
!     ---------------
273
!     Local variables
274
!     ---------------
275
!
276
      REAL(KIND=RP), ALLOCATABLE :: rangeCuts(:)
6✔
277
      INTEGER                    :: nBreaks
278
      INTEGER                    :: j
279
      REAL(KIND=RP)              :: limit = 1.0d-14
280
      REAL(KIND=RP), ALLOCATABLE :: tmpA(:)
6✔
281

282
      nBreaks = SIZE(breaks) - 1
6✔
283
!
284
!     -------------------------------------------------------------
285
!     There will be at least one, and possibly more forced segments
286
!     -------------------------------------------------------------
287
!
288
      CALL FindOptimizedCutsInRange(curve, polyOrder, [breaks(0), breaks(1)-Limit], &
289
                                    options, gQuad, tmpA)
18✔
290
      breakIndices = [SIZE(tmpA)]
12✔
291
      tmpA(UBOUND(tmpA)) = breaks(1)
18✔
292
      IF(nBreaks == 1) THEN
6✔
293
         ALLOCATE(optimizedCuts(0:SIZE(tmpA)-1))
4✔
294
         optimizedCuts(0:) = tmpA
47✔
295
         RETURN
4✔
296
      END IF
297
!
298
!     ----------------------------
299
!     Otherwise, continue marching
300
!     ----------------------------
301
!
302
      DO j = 2, nBreaks
5✔
303
         CALL FindOptimizedCutsInRange(curve, polyOrder, [breaks(j-1)+limit, breaks(j)-limit], &
304
                                       options, gQuad, rangeCuts)
9✔
305
         breakIndices  = [breakIndices,breakIndices(j-1) + SIZE(rangeCuts) - 1]
21✔
306
         rangeCuts(UBOUND(rangeCuts)) = breaks(j)
9✔
307
         tmpA = [tmpA, rangeCuts(1:)]
99✔
308
         DEALLOCATE(rangeCuts)
5✔
309
      END DO
310
      ALLOCATE(optimizedCuts(0:SIZE(tmpA)-1))
2✔
311
      optimizedCuts(0:) = tmpA
24✔
312

313
   END SUBROUTINE FindOptimizedCuts
6✔
314
!
315
!////////////////////////////////////////////////////////////////////////
316
!
317
   SUBROUTINE FindOptimizedCutsInRange(curve, polyOrder, tRange, options, gQuad, optimizedCuts)
9✔
318
!
319
!  ----------------------------------------------------------------------------------
320
!  March along the curve to find the length of each
321
!  segment so that the H^1 error is less than the tolerance allowed for that segment.
322
!  ----------------------------------------------------------------------------------
323
!
324
      IMPLICIT NONE
325
!
326
!     ---------
327
!     Arguments
328
!     ---------
329
!
330
      CLASS(SMCurve), POINTER    :: curve              ! The curve to be approximated
331
      INTEGER                    :: polyOrder          ! polynomial order of the new approximation
332
      REAL(KIND=RP)              :: tRange(2)          ! Location of forced cut points, starting with t = 0.0
333
      TYPE(OptimizerOptions)     :: options            ! parameters for the approximation
334
      TYPE(GaussQuadratureType)  :: gQuad
335
      REAL(KIND=RP), ALLOCATABLE :: optimizedCuts(:)
336
!
337
!     ---------------
338
!     Local variables
339
!     ---------------
340
!
341
      LOGICAL                                :: steppingNotDone
342
      REAL(KIND=RP)                          :: cuts(0:1)
343
      REAL(KIND=RP)                          :: iterToler = 1.0d-6
344
      REAL(KIND=RP)                          :: fR, h, f, h2
345
      REAL(KIND=RP)                          :: tL, tMid, tR
346
      REAL(KIND=RP)                          :: e, hStar, cFactor, targetError
347
      INTEGER                                :: k, nCuts
348

349
      ALLOCATE(optimizedCuts(0:1))
9✔
350
      optimizedCuts = tRange  ! will be added to as marching progresses
36✔
351
!
352
!     ---------------------------------------
353
!     Loop until hit the end at t = tRange(2)
354
!     ---------------------------------------
355
!
356
      tL              = tRange(1)
9✔
357
      tR              = tRange(2)
9✔
358
      cuts            = [tL, tR] ! = [t_{k-1},t_k]
27✔
359
      h               = tR - tL
9✔
360
!
361
!     ------------------------------------------------------------------
362
!     If the approximation already satisfies the approximation tolerance
363
!     then the marchingfunction will be negative. Positive means
364
!     the tolerance is not met and a search must be done
365
!     fR = marchingfunction(curve, polyOrder, cuts, options, gQuad)
366
!     ------------------------------------------------------------------
367
!
368
      fR = marchingfunction(curve, polyOrder, cuts, options, gQuad)
9✔
369
      IF(fr .le. 0.0) RETURN
9✔
370
!
371
!     --------------------------
372
!     Find the intervals in turn
373
!     --------------------------
374
!
375
      steppingNotDone = .TRUE.
8✔
376
      k               = 0 ! Segment number
8✔
377

378
      DO WHILE(steppingNotDone)
50✔
379
         k    = k + 1
50✔
380
         tMid = iterate(tL, tR, curve, polyOrder, options, gQuad, iterToler, RIGHT_SIDE)
50✔
381
!
382
!        -------------------------------------------------
383
!        converged... add this point to the segments array
384
!        and prepare for the next
385
!        -------------------------------------------------
386
!
387
         IF (ABS(tMid - tRange(2)) <= iterToler) EXIT
50✔
388
         CALL addCut(cuts = optimizedCuts, valueToAdd = tMid, atIndex = k)
50✔
389
         tL   = tMid
50✔
390
         tR   = tRange(2)
50✔
391
         cuts = [tL,tR]
150✔
392
!
393
!        ----------------------------------------------------
394
!        We're done if the remaining segment has small enough
395
!        errors
396
!        ----------------------------------------------------
397
!
398
         f = marchingFunction(curve, polyOrder, cuts, options, gQuad)
50✔
399
         IF(f .le. 0.0_RP)    EXIT
50✔
400

401
      END DO !Stepping
402
!
403
!     --------------------------------------------------------------------
404
!     The last segment may be very small since the marching knows nothing
405
!     about what lies ahead. Use the last error and the fact that the
406
!     the H^1 error should vary like h^{p} to estimate the segment size
407
!     needed to produce the desired error. Choose the smaller of this
408
!     segment size and half of the last two segments to balance the error
409
!     in the last two segments. Don't bother with changes that are less
410
!     than 25%, which is a completely arbitrary number.
411
!     --------------------------------------------------------------------
412
!
413
       nCuts       = SIZE(optimizedCuts) - 1
8✔
414
       h2          = optimizedCuts(nCuts) - optimizedCuts(nCuts-2)
8✔
415
       h           = optimizedCuts(nCuts) - optimizedCuts(nCuts-1)
8✔
416
       cuts        = [optimizedCuts(nCuts-1), optimizedCuts(nCuts)]
24✔
417
       targetError = options % safetyFactor*options % toler
8✔
418
       e           = marchingFunction(curve, polyOrder, cuts, options, gQuad) + targetError
8✔
419
       cFactor     = (targetError/(e + 1.0d-14))**(1.0_RP/polyOrder)
8✔
420

421
       IF ( cFactor > 1.25_RP )     THEN
8✔
422
          hStar       = h*cFactor
6✔
423
          optimizedCuts(nCuts-1) = optimizedCuts(nCuts) - MIN(hStar, 0.5_RP*h2)
6✔
424
       END IF
425

426
   END SUBROUTINE FindOptimizedCutsInRange
427
!
428
!////////////////////////////////////////////////////////////////////////
429
!
430
!
431
!////////////////////////////////////////////////////////////////////////
432
!
433
   REAL(KIND=RP) FUNCTION iterate(tL, tR, curve, polyOrder, options, gQuad, iterToler, side)
50✔
434
   !
435
   !  -----------------------------------------------------------------------------------
436
   !  Iterate to find the point t_{k+1} such that the error on [t_k,t_{k+1}] is less than
437
   !  iterToler.
438
   !  -----------------------------------------------------------------------------------
439
   !
440
         IMPLICIT NONE
441
   !
442
   !     ---------
443
   !     Arguments
444
   !     ---------
445
   !
446
         REAL(KIND=RP)             :: tL, tR
447
         CLASS(SMCurve), POINTER   :: curve              ! The curve to be approximated
448
         INTEGER                   :: polyOrder          ! polynomial order of the new approximation
449
         TYPE(OptimizerOptions)    :: options            ! parameters for the approximation
450
         TYPE(GaussQuadratureType) :: gQuad
451
         REAL(KIND=RP)             :: iterToler
452
         INTEGER                   :: side
453
   !
454
   !     ---------------
455
   !     Local variables
456
   !     ---------------
457
   !
458
         REAL(KIND=RP)   :: cuts(0:1)
459
         REAL(KIND=RP)   :: fL, fR
460
         REAL(KIND=RP)   :: tN, tNm1, fN, fNm1, tNp1, fNp1
461
         INTEGER         :: maxIterB = 6 ! Should get to 0.016
462
         INTEGER         :: maxIterS = 8 ! Should get to iterToler
463
         INTEGER         :: maxIter
464
         INTEGER         :: n
465
         LOGICAL         :: done
466

467
         cuts = [tL, tR] ! = [t_{k-1},t_k]
150✔
468
         iterate = bisect(maxIterB, side, done)
50✔
469
         IF ( done ) RETURN
50✔
470
   !
471
   !     -----------------------------------
472
   !     Pegasus method to finish
473
   !     (fast modification of Regula Falsi)
474
   !
475
   !     See paper
476
   !     Dowell, M., Jarratt, P. (1972)
477
   !     The “Pegasus” method for computing
478
   !       the root of an equation.
479
   !     https://doi.org/10.1007/BF01932959
480
   !     -----------------------------------
481
   !
482
         tN   = tR; fN   = fR
50✔
483
         tNm1 = tL; fNm1 = fL
50✔
484

485
       ! Important note. This assumes that
486
         ! fN and fNm1 have opposite sign, otherwise it won't bracket.
487
         DO n = 1, maxIterS
141✔
488
          ! Regula Falsi position
489
            ! (Intersection of secant line with the t axis)
490
          ! tNp1 = tN - (tN - tNm1) * fN / (fN - fNm1)
491
            tNp1 = (fN * tNm1 - tN * fNm1) / (fN - fNm1)
141✔
492
            cuts(1) = tNp1
141✔
493
            fNp1    = marchingFunction(curve, polyOrder, cuts, options, gQuad)
141✔
494

495
            ! Check for convergence
496
            IF ( ( ABS(fNp1) <= iterToler ) .OR. ( ABS(tN - tNm1) <= iterToler ) )    THEN
141✔
497
               iterate = tNp1
50✔
498
               RETURN
50✔
499
            END IF
500

501
            ! Update endpoints
502
            IF ( fNm1 * fNp1 < 0.0_RP )    THEN
91✔
503
               ! tNm1 stays the same
504
               ! Pegasus scaling of left function value
505
               fNm1 = fNm1 * fN / (fN + fNp1)
20✔
506

507
               ! Replace right endpoint
508
               tN = tNp1
20✔
509
               fN = fNp1
20✔
510
            ELSE
511
               ! tN stays the same
512
               ! Pegasus scaling of right function value
513
               fN = fNm1 * fN / (fNm1 + fNp1)
71✔
514

515
               ! Replace left endpoint
516
               tNm1 = tNp1
71✔
517
               fNm1 = fNp1
71✔
518
            END IF
519
         END DO
520
   !
521
   !     ---------------------------------------------------------------
522
   !     If Pegasus doesn't converge, we get to here. Just use bisection
523
   !     to finish the job.
524
   !     ---------------------------------------------------------------
525
   !
NEW
526
         maxIter = NINT(LOG((tR - tL)/iterToler)/LOG(2.0_RP))+1
×
NEW
527
         iterate = bisect(maxIter, side, done)
×
528
   !
529
   !     ========
530
         CONTAINS
531
   !     ========
532
   !
533
   !////////////////////////////////////////////////////////////////////////
534
   !
535
      REAL(KIND=RP) FUNCTION bisect(maxIter, side, done)
50✔
536
   !
537
   !  -----------------------------------------------------------------------------------
538
   !  Iterate to find the point t_{k+1} such that the error on [t_k,t_{k+1}] is less than
539
   !  iterToler.
540
   !  -----------------------------------------------------------------------------------
541
   !
542
         IMPLICIT NONE
543
   !
544
   !     ---------
545
   !     Arguments
546
   !     ---------
547
   !
548
         INTEGER                   :: maxIter
549
         LOGICAL                   :: done
550
         INTEGER                   :: side
551
   !
552
   !     ---------------
553
   !     Local variables
554
   !     ---------------
555
   !
556
         REAL(KIND=RP)   :: tMid, h
557
         REAL(KIND=RP)   :: fMid
558
         INTEGER         :: n
559

560
   !
561
   !    -----------------------------------------------------
562
   !    Iterate on error function until tolerance is satisfied
563
   !    -----------------------------------------------------
564
   !
565
         fL = - options % safetyFactor*options % toler ! the best case. See marchingFunction
50✔
566
   !
567
   !     ---------
568
   !     Bisection
569
   !     ---------
570
   !
571
         done = .FALSE.
50✔
572
         DO n = 1, maxIter
350✔
573
            h       = tR - tL
300✔
574
            tMid    = tL + 0.5_RP*h
300✔
575
            cuts(side) = tMid
300✔
576
            fMid = marchingFunction(curve, polyOrder, cuts, options, gQuad)
300✔
577

578
            IF(0.5_RP*ABS(h) .le. iterToler) THEN ! If marching R -> L, h < 0
300✔
NEW
579
               bisect = tMid
×
NEW
580
               done   = .TRUE.
×
NEW
581
               RETURN
×
582
            END IF
583

584
            IF ( fMid*fL < 0.0_RP )     THEN
350✔
585
               tR = tMid
174✔
586
               fR = fMid
174✔
587
            ELSE
588
               tL = tMid
126✔
589
               fL = fMid
126✔
590
            END IF
591
         END DO
592

593
         bisect = tMid
50✔
594

595
      END FUNCTION bisect
50✔
596

597
      END FUNCTION iterate
598
!
599
!////////////////////////////////////////////////////////////////////////
600
!
601
   FUNCTION marchingFunction(curve, polyOrder, cuts, options, gQuad) RESULT(f)
508✔
602
      IMPLICIT NONE
603
!
604
!     ----------
605
!     Arguments
606
!     ----------
607
!
608
      CLASS(SMCurve), POINTER    :: curve              ! The curve to be approximated
609
      INTEGER                    :: polyOrder          ! polynomial order of the new approximation
610
      REAL(KIND=RP)              :: cuts(0:1)
611
      TYPE(OptimizerOptions)     :: options            ! parameters for the marching algorithm
612
      TYPE(GaussQuadratureType)  :: gQuad
613
!
614
!     ------------
615
!     Return value
616
!     ------------
617
!
618
      REAL(KIND=RP) :: f
619
!
620
!     ---------------
621
!     Local variables
622
!     ---------------
623
!
624
      REAL(KIND=RP), ALLOCATABLE             :: errors(:,:)
508✔
625
      CALL curveSegmentErrors(curve, polyOrder, cuts, options, gQuad, errors)
508✔
626
      f = errors(1,USER_NORM) - options % safetyFactor*options % toler
508✔
627

628
   END FUNCTION marchingFunction
508✔
629
!
630
!////////////////////////////////////////////////////////////////////////
631
!
632
   SUBROUTINE curveSegmentErrors(curve, polyOrder, cuts, options, gQuad, errors)
508✔
633
      IMPLICIT NONE
634
!
635
!     ----------
636
!     Arguments
637
!     ----------
638
!
639
      CLASS(SMCurve), POINTER    :: curve              ! The curve to be approximated
640
      INTEGER                    :: polyOrder          ! polynomial order of the new approximation
641
      REAL(KIND=RP)              :: cuts(0:1)
642
      TYPE(GaussQuadratureType)  :: gQuad
643
      REAL(KIND=RP), ALLOCATABLE :: errors(:,:)
644
!
645
!     ---------------
646
!     Local variables
647
!     ---------------
648
!
649
      CLASS(SMCurve)               , POINTER :: optimizedCurve
650
      CLASS(MultiSegmentModalCurve), POINTER :: msmCurve
651
      TYPE(OptimizerOptions)                 :: options  ! parameters for the marching algorithm
652
      INTEGER, ALLOCATABLE                   :: breakIndices(:)
508✔
653

654
      breakIndices = [0]
1,016✔
655
      CALL OptimizeCurve(curve        = curve,                   &
656
                         polyOrder    = polyOrder,               &
657
                         cuts         = cuts,                    &
658
                         breakIndices = breakIndices,            &
659
                         options      = options,                 &
660
                         newName      = "tmp",                   &
661
                         newID        = 0,                       &
662
                         optimized    = optimizedCurve)
508✔
663

664
      CALL castToMultiSegmentModalCurve(optimizedCurve, msmCurve)
508✔
665
      CALL SegmentErrors(exact          = curve,    &
666
                         segmentedCurve = msmCurve, &
667
                         gQuad          = gQuad,    &
668
                         errors         = errors)
508✔
669

670
      CALL releaseBaseCurve(optimizedCurve)
508✔
671

672
   END SUBROUTINE curveSegmentErrors
508✔
673
!
674
!////////////////////////////////////////////////////////////////////////
675
!
676
   SUBROUTINE curveSegmentLengths(curve, polyOrder, cuts, options, gQuad, lengths)
1✔
677
!
678
!  -----------------------------------------------------
679
!  Collect the arc lengths of the optimal curve segments
680
!  -----------------------------------------------------
681
!
682
      IMPLICIT NONE
683
!
684
!     ----------
685
!     Arguments
686
!     ----------
687
!
688
      CLASS(SMCurve), POINTER    :: curve              ! The curve to be approximated
689
      INTEGER                    :: polyOrder          ! polynomial order of the new approximation
690
      REAL(KIND=RP)              :: cuts(0:)
691
      TYPE(GaussQuadratureType)  :: gQuad
692
      REAL(KIND=RP), ALLOCATABLE :: lengths(:)
693
!
694
!     ---------------
695
!     Local variables
696
!     ---------------
697
!
698
      CLASS(SMCurve)               , POINTER :: optimizedCurve
699
      CLASS(MultiSegmentModalCurve), POINTER :: msmCurve
700
      TYPE(OptimizerOptions)                 :: options  ! parameters for the marching algorithm
701
      INTEGER, ALLOCATABLE                   :: breakIndices(:)
1✔
702
      INTEGER                                :: nSegments
703
      INTEGER                                :: j
704
!
705
!     ------------------------------
706
!     find the optimal approximation
707
!     ------------------------------
708
!
709
      breakIndices = [0]
2✔
710
      CALL OptimizeCurve(curve        = curve,                   &
711
                         polyOrder    = polyOrder,               &
712
                         cuts         = cuts,                    &
713
                         breakIndices = breakIndices,            &
714
                         options      = options,                 &
715
                         newName      = "tmp",                   &
716
                         newID        = 0,                       &
717
                         optimized    = optimizedCurve)
1✔
718

719
      CALL castToMultiSegmentModalCurve(optimizedCurve, msmCurve)
1✔
720
!
721
!     ---------------------------------------
722
!     Compute the arc lengths of each segment
723
!     ---------------------------------------
724
!
725
      nSegments = msmCurve % nSegments
1✔
726
      ALLOCATE(lengths(nSegments))
1✔
727
      DO j = 1, nSegments
3✔
728
         lengths(j) = segmentArcLength(msmCurve, j ,gQuad % nodes, gQuad % weights)
3✔
729
      END DO
730
      CALL releaseBaseCurve(optimizedCurve)
1✔
731

732
   END SUBROUTINE curveSegmentLengths
1✔
733
!
734
!////////////////////////////////////////////////////////////////////////
735
!
736
   SUBROUTINE SegmentErrors(exact, segmentedCurve, gQuad, errors)
514✔
737
!
738
!  -------------------------------------------------------------------
739
!  Compute the H^1 errors in each of the segments of a segmented curve
740
!  -------------------------------------------------------------------
741
!
742
      IMPLICIT NONE
743
!
744
!     ---------
745
!     Arguments
746
!     ---------
747
!
748
      CLASS(SMCurve)               , POINTER :: exact
749
      CLASS(MultiSegmentModalCurve), POINTER :: segmentedCurve
750
      TYPE(GaussQuadratureType)              :: gQuad
751
      REAL(KIND=RP), ALLOCATABLE             :: errors(:,:)
752
!
753
!     ----------------
754
!     Local variables
755
!     ----------------
756
!
757
      INTEGER       :: nSegments
758
      INTEGER       :: k
759

760
      nSegments = segmentedCurve % nSegments
514✔
761
      ALLOCATE(errors(nSegments,3), source = 0.0_RP)
3,748✔
762

763
      DO k = 1, nSegments
1,078✔
764
         errors(k,:) =  segmentError(k, exact, segmentedCurve, gQuad)
2,770✔
765
      END DO
766

767
   END SUBROUTINE SegmentErrors
514✔
768
!
769
!////////////////////////////////////////////////////////////////////////
770
!
771
   FUNCTION segmentError(k, exact, segmentedCurve, gQuad) RESULT(er)
564✔
772
!
773
!  ---------------------------------------------------------------
774
!  Compute the H^1 errors on the k'th segment of a segmented curve
775
!  ---------------------------------------------------------------
776
!
777
      IMPLICIT NONE
778
!
779
!     ---------
780
!     Arguments
781
!     ---------
782
!
783
      INTEGER                                :: k
784
      CLASS(SMCurve)               , POINTER :: exact
785
      CLASS(MultiSegmentModalCurve), POINTER :: segmentedCurve
786
      TYPE(GaussQuadratureType)              :: gQuad
787
      REAL(KIND=RP)                          :: er(3)
788
!
789
!     ----------------
790
!     Local variables
791
!     ----------------
792
!
793
      INTEGER       :: qOrder
794
      INTEGER       :: j
795
      REAL(KIND=RP) :: t, h, dsdt
796
      REAL(KIND=RP) :: e, e0(3), e1(3), eMax, eMaxDeriv
797

798
      qOrder = gQuad % N
564✔
799

800
      e         = 0.0_RP
564✔
801
      eMax      = 0.0_RP
564✔
802
      eMaxDeriv = 0.0_RP
564✔
803
      h         = segmentedCurve % cuts(k) - segmentedCurve % cuts(k-1)
564✔
804
      dsdt      = 2.0_RP/h
564✔
805
      DO j = 0, qOrder
13,884✔
806
         t    = segmentedCurve % cuts(k-1) + h*0.5_RP*(gQuad % nodes(j) + 1.0_RP)
13,320✔
807
         e0   = (exact % positionAt(t)   - segmentedCurve % valueInSegment(k, t, which = LA_EVALUATE_FUNCTION))**2
53,280✔
808
         e1   = (exact % derivativeAt(t) - dsdt*segmentedCurve % valueInSegment(k, t, which = LA_EVALUATE_DERIVATIVE))**2
53,280✔
809

810
         e         = e + (e0(1) + e0(2) + e1(1) + e1(2))*gQuad % weights(j)
13,320✔
811
         eMax      = MAX(eMax,e0(1),e0(2))
13,320✔
812
         eMaxDeriv = MAX(eMaxDeriv,e1(1),e1(2))
13,884✔
813
      END DO
814
      er(USER_NORM)       = SQRT(0.5_RP*h*e)
564✔
815
      er(MAX_NORM)        = SQRT(eMax)
564✔
816
      er(MAX_NORM_DERIV)  = SQRT(eMaxDeriv)
564✔
817

818
   END FUNCTION segmentError
564✔
819
!
820
!////////////////////////////////////////////////////////////////////////
821
!
822
   SUBROUTINE addCut(cuts, valueToAdd, atIndex)
50✔
823
!
824
!     ----------------------------------------------------------
825
!     Cuts is dimensioned as (0:N), but there seems to be no way
826
!     with an allocatable to note that in the declaration here.
827
!     So work with shifted indices, shifted by 1
828
!     ----------------------------------------------------------
829
!
830
      IMPLICIT NONE
831
!
832
!     ---------
833
!     Arguments
834
!     ---------
835
!
836
      REAL(KIND=RP), ALLOCATABLE :: cuts(:)
837
      REAL(KIND=RP) :: valueToAdd
838
      INTEGER       :: atIndex
839
!
840
!     ---------------
841
!     Local variables
842
!     ---------------
843
!
844
      REAL(KIND=RP), ALLOCATABLE :: newCuts(:)
50✔
845
      INTEGER                    :: N
846

847
      N = SIZE(cuts)
50✔
848
      ALLOCATE(newCuts(0:N))
50✔
849
      newCuts(0:atIndex-1)  = cuts(0:atIndex-1)
300✔
850
      newCuts(atIndex+1:)   = cuts(atIndex:)
100✔
851
      newCuts(atIndex)      = valueToAdd
50✔
852

853
      CALL MOVE_ALLOC(FROM = newCuts, TO = cuts)
50✔
854

855
   END SUBROUTINE addCut
50✔
856
!
857
!////////////////////////////////////////////////////////////////////////
858
!
NEW
859
   SUBROUTINE AppendRealArray(base, arrayToAdd)
×
860
      IMPLICIT NONE
861
      REAL(KIND=RP), ALLOCATABLE :: base(:)
862
      REAL(KIND=RP)              :: arrayToAdd(:)
NEW
863
      base = [base,arrayToAdd]
×
864

NEW
865
   END SUBROUTINE AppendRealArray
×
866
!
867
!////////////////////////////////////////////////////////////////////////
868
!
869
!   SUBROUTINE OptimizeCurveWithSubdivisions(curve, polyOrder, options, newName, newID, optimized)
870
!      IMPLICIT NONE
871
!!
872
!!     ---------
873
!!     Arguments
874
!!     ---------
875
!!
876
!      CLASS(SMCurve), POINTER :: curve              ! The curve to be approximated
877
!      CLASS(SMCurve), POINTER :: optimized          ! The new approximation
878
!      INTEGER                 :: polyOrder          ! polynomial order of the new approximation
879
!      CHARACTER(LEN=*)        :: newName            ! Name of the new curve
880
!      INTEGER                 :: newID              ! ID of the new curve
881
!      TYPE(OptimizerOptions)  :: options            ! parameters for the approximation
882
!!
883
!!     ---------------
884
!!     Local variables
885
!!     ---------------
886
!!
887
!      INTEGER                                :: lev, j, i
888
!      INTEGER                                :: offset
889
!      INTEGER                                :: nSegments
890
!      REAL(KIND=RP), ALLOCATABLE             :: cuts(:)
891
!      CLASS(SMCurve)               , POINTER :: optimizedCurve
892
!      CLASS(MultiSegmentModalCurve), POINTER :: msmCurve
893
!      TYPE(GaussQuadratureType)              :: gQuad
894
!      REAL(KIND=RP), ALLOCATABLE             :: errors(:)
895
!      REAL(KIND=RP)                          :: tMid
896
!      LOGICAL                                :: subdivisionIsFinished
897
!
898
!      CALL ConstructGaussQuadrature(gQuad, 4*polyOrder)
899
!!
900
!!     ------------------
901
!!     Start at top level
902
!!     ------------------
903
!!
904
!      nSegments = 1
905
!      ALLOCATE(cuts(0:nSegments))
906
!      cuts = [0.0_RP, 1.0_RP]
907
!
908
!      CALL OptimizeCurve(curve     = curve,                       &
909
!                         polyOrder = polyOrder,                   &
910
!                         cuts      = cuts,                        &
911
!                         options   = options,                     &
912
!                         newName   = newName,                     &
913
!                         newID     = newID,                       &
914
!                         optimized = optimizedCurve)
915
!
916
!      CALL castToMultiSegmentModalCurve(optimizedCurve, msmCurve)
917
!      CALL SegmentErrors(exact          = curve,    &
918
!                         segmentedCurve = msmCurve, &
919
!                         gQuad          = gQuad,    &
920
!                         errors         = errors)
921
!      DO lev = 1, MAX_DEPTH
922
!         subdivisionIsFinished = .TRUE.
923
!!
924
!!        -------------------------------------
925
!!        Compute new subdivisions as necessary
926
!!        -------------------------------------
927
!!
928
!         offset = 0
929
!         DO j = 1, UBOUND(errors,1)
930
!            i = j + offset
931
!            IF ( errors(j) > options % toler )     THEN
932
!               tMid = 0.5_RP*(cuts(i) + cuts(i-1))
933
!               CALL addCut(cuts,valueToAdd = tMid, atIndex = i)
934
!               offset = offset + 1
935
!               subdivisionIsFinished = .FALSE.
936
!            END IF
937
!         END DO
938
!         IF(subdivisionIsFinished) EXIT
939
!!
940
!!        ----------------------------------
941
!!        Compute curves on new subdivisions
942
!!        ----------------------------------
943
!!
944
!         CALL releaseBaseCurve(optimizedCurve)
945
!         DEALLOCATE(errors)
946
!         CALL OptimizeCurve(curve              = curve,              &
947
!                            polyOrder          = polyOrder,          &
948
!                            cuts               = cuts,               &
949
!                            options            = options,            &
950
!                            newName            = newName,            &
951
!                            newID              = newID,              &
952
!                            optimized          = optimizedCurve)
953
!
954
!         CALL castToMultiSegmentModalCurve(optimizedCurve, msmCurve)
955
!         CALL SegmentErrors(exact          = curve,    &
956
!                            segmentedCurve = msmCurve, &
957
!                            gQuad          = gQuad,    &
958
!                            errors         = errors)
959
!
960
!      END DO
961
!      optimized => optimizedCurve
962
!
963
!   END SUBROUTINE OptimizeCurveWithSubdivisions
964

965
   END Module CurveOptimization
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc