1!/*****************************************************************************/
2! *
3! *  Elmer, A Finite Element Software for Multiphysical Problems
4! *
5! *  Copyright 1st April 1995 - , CSC - IT Center for Science Ltd., Finland
6! *
7! *  This library is free software; you can redistribute it and/or
8! *  modify it under the terms of the GNU Lesser General Public
9! *  License as published by the Free Software Foundation; either
10! *  version 2.1 of the License, or (at your option) any later version.
11! *
12! *  This library is distributed in the hope that it will be useful,
13! *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14! *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15! *  Lesser General Public License for more details.
16! *
17! *  You should have received a copy of the GNU Lesser General Public
18! *  License along with this library (in file ../LGPL-2.1); if not, write
19! *  to the Free Software Foundation, Inc., 51 Franklin Street,
20! *  Fifth Floor, Boston, MA  02110-1301  USA
21! *
22! *****************************************************************************/
23!
24! *
25! *  Authors: Juhani Kataja
26! *
27!
28! ****************************************************************************/
29
30MODULE zirka ! Pointwise zirka {{{
31USE ISO_C_BINDING, ONLY: C_INT, C_LOC, C_PTR, C_F_POINTER
32USE GeneralUtils
33USE DefUtils
34implicit none
35
36private
37
38INTEGER, PARAMETER :: STACKSIZEINCREMENT = 20
39INTEGER, PARAMETER :: POS_SATURATION = 1
40INTEGER, PARAMETER :: NEG_SATURATION = -1
41INTEGER, PARAMETER :: NO_SATURATION = 0
42
43TYPE, public :: SplineLoop_t  ! {{{
44  REAL(KIND=dp), allocatable :: BHasc(:,:), BHdesc(:,:), BHsat(:,:)
45  REAL(KIND=dp), pointer :: r_asc(:) => NULL(), r_desc(:) => NULL() , r_sat(:) => NULL() ! Needs to be pointer required by InterpolateCurve
46  real(kind=dp), private :: BLimits(2)
47  LOGICAL, private :: newmethod = .false.
48  CONTAINS
49  PROCEDURE, private :: eval_1 => EvalSplineLoop
50  procedure, private :: eval_2 => EvalSplineLoopSingle
51  generic, public :: eval => eval_1, eval_2
52END TYPE SplineLoop_t ! }}}
53
54TYPE :: RevCurve_t  ! {{{
55  REAL(kind=dp) :: Bp, Bq
56  REAL(kind=dp) :: a, b, c, dBrev, dHrev
57  INTEGER :: depth ! if 0, this is ascending or descending master curve depending on Bp and Bq
58  CLASS(RevCurve_t), POINTER :: parent => NULL()
59  TYPE(SplineLoop_t), POINTER :: bigloop => NULL()
60  PROCEDURE(SimpleEvalRevCurve), POINTER :: simple_eval => SimpleEvalRevCurve
61  CONTAINS
62  ! PROCEDURE :: simple_eval => SimpleEvalRevCurve
63  PROCEDURE :: eval => RecurEvalCurve
64END TYPE RevCurve_t ! }}}
65
66type, public :: ZirkaABC_t ! {{{
67  REAL(KIND=dp), dimension(1:4) :: Coeffs = [7.73,2.76,-28.63,28.36]
68  REAL(KIND=dp) :: b_mult = 0.22
69  REAL(KIND=dp) :: c_mult = 0.125
70  contains
71  procedure, public :: GetABC
72end type ! }}}
73
74TYPE, public  :: MasterCurve_t  ! {{{
75  TYPE(RevCurve_t), POINTER :: children(:) ! We should always have revcurve_t % depth + 2 to be right index here..
76  CLASS(RevCurve_t), POINTER, public :: Head => NULL()
77  TYPE(RevCurve_t), POINTER, private :: rc_asc => NULL(), rc_desc => NULL() !  Should this a pointer?
78  REAL(KIND=dp), PUBLIC :: B0(3)
79  REAL(KIND=dp), ALLOCATABLE, PRIVATE :: BH(:,:)
80  REAL(KIND=dp), POINTER, PRIVATE :: r_bh(:) => null()
81  TYPE(ZirkaABC_t), POINTER, PRIVATE :: ABCparams => NULL()
82  integer, private :: CacheSubSample  = 2
83  integer, private :: saturation
84  contains
85  procedure, public :: eval => mc_eval
86  procedure, public :: printme => mc_printme
87  procedure, public :: addstack => mc_addstack
88  procedure, public :: drive => HBDrive
89  procedure, public :: insaturation
90END TYPE MasterCurve_t ! }}}
91
92TYPE, public :: GlobalHysteresisModel_t ! {{{
93  TYPE(MasterCurve_t), ALLOCATABLE :: Curves(:,:)
94  TYPE(SplineLoop_t), POINTER :: Masterloop => NULL()
95  TYPE(ZirkaABC_t), POINTER :: ABCparams => NULL()
96  integer :: CacheSubSample
97  logical :: initialized = .false.
98END TYPE GlobalHysteresisModel_t ! }}}
99
100
101interface MasterCurve_t
102  module procedure init_master_curve
103end interface
104
105interface printeval
106  module procedure rc_printeval, mc_printeval
107end interface
108
109public :: InitSplineLoop, printeval
110
111CONTAINS
112
113!-------------------------------------------------------------------------------
114!> Returns true if rc is ascending
115PURE FUNCTION ascending(rc) result(Asc) ! {{{
116  CLASS(RevCurve_t), intent(in) :: rc
117  LOGICAL :: asc
118!-------------------------------------------------------------------------------
119  if (rc % Bp < rc % Bq) then
120    asc = .true.
121    return
122  end if
123  asc = .false.
124END FUNCTION ! }}}
125!-------------------------------------------------------------------------------
126
127!-------------------------------------------------------------------------------
128!> Returns true if rc is descending
129PURE FUNCTION descending(rc) result(desc) ! {{{
130  CLASS(RevCurve_t), intent(in) :: rc
131  LOGICAL :: desc
132!-------------------------------------------------------------------------------
133  desc = .not. ascending(rc)
134END FUNCTION ! }}}
135!-------------------------------------------------------------------------------
136
137!-------------------------------------------------------------------------------
138!> Returns saturatino value of MasterCurve_t mc
139FUNCTION InSaturation(mc, B) RESULT(saturation) ! {{{
140  class(MasterCurve_t) :: mc
141  real(kind=dp) :: B
142  integer :: saturation
143  if (B < mc % head % bigloop % blimits(1)) then
144    saturation = NEG_SATURATION
145    return
146  end if
147  if (B > mc % head % bigloop % blimits(2)) then
148    saturation = POS_SATURATION
149    return
150  end if
151  saturation = NO_SATURATION
152END FUNCTION InSaturation ! }}}
153!-------------------------------------------------------------------------------
154
155!-------------------------------------------------------------------------------
156!> Returns a-b-c parameters appearing in the Zirka model
157SUBROUTINE GetABC(this, dBout, dBrev, a, b, c) ! {{{
158  IMPLICIT NONE
159  !-------------------------------------------------------------------------------
160  class(ZirkaABC_t) :: this
161  REAL(KIND=dp), INTENT(IN) :: dBout, dBrev
162  REAL(KIND=dp), INTENT(OUT) :: a, b, c
163  !-------------------------------------------------------------------------------
164  REAL(KIND=dp) :: beta
165  INTEGER :: k
166  !-------------------------------------------------------------------------------
167
168  beta = dBrev/dBout
169
170  a = 0.0_dp
171  DO k = 1,4
172    a = a + this % Coeffs(k)*(beta**(k-1))
173  END DO
174  a = a * dBrev
175  b = this % b_mult*(1-beta)
176  c = this % c_mult
177
178END SUBROUTINE ! }}}
179!-------------------------------------------------------------------------------
180
181!-------------------------------------------------------------------------------
182!> Initialize spline loop from ascending hysteretic curve and single
183!> valued curve for saturation region
184!> BHasc and BHsingle are assumed to be of shape (N,2)
185!-------------------------------------------------------------------------------
186FUNCTION InitSplineLoop(BHasc, BHsingle) RESULT(Loop) ! {{{
187  !-------------------------------------------------------------------------------
188  IMPLICIT NONE
189  REAL(KIND=dp), intent(in) :: BHasc(:,:), BHsingle(:,:)
190  TYPE(SplineLoop_t), POINTER :: Loop
191  !-------------------------------------------------------------------------------
192  INTEGER :: N, M
193  N = size(BHasc,1)
194  M = size(BHsingle,1)
195
196  allocate(loop)
197  allocate(loop % BHasc(n, 2), loop % BHdesc(n,2), loop%BHsat(m,2), &
198      loop%r_asc(n), loop % r_desc(n), loop % r_sat(m))
199
200  loop % BHasc(:,1:2) = BHasc(:,1:2)
201  loop % BHdesc(:,1) = -BHasc(N:1:-1,1)
202  loop % BHdesc(:,2) = -BHasc(N:1:-1,2)
203  loop % BHsat = BHsingle
204
205  CALL CubicSpline(N, loop % BHasc(1:N, 1), loop % BHasc(1:N, 2), loop % r_asc)
206  CALL CubicSpline(N, loop % BHdesc(1:N, 1), loop % BHdesc(1:N, 2), loop % r_desc)
207  CALL CubicSpline(M, loop % BHsat(1:M, 1), loop % BHsat(1:M, 2), loop % r_sat)
208  loop % newmethod = .true.
209  loop % blimits(1) = bhasc(1,1)
210  loop % blimits(2) = - loop % blimits(1)
211
212END FUNCTION InitSplineLoop ! }}}
213
214function init_master_curve(bigloop, ABCParams, &
215      init, b0, initseq, n_cachesubsample)  result(mc)!  {{{
216  IMPLICIT NONE
217  TYPE(MasterCurve_t) :: mc
218  TYPE(ZirkaABC_t), POINTER :: ABCParams
219  TYPE(RevCurve_t), POINTER :: rca, rcd
220  TYPE(SplineLoop_t), POINTER :: bigloop
221  REAL(KIND=dp), optional, intent(in) :: B0(3)
222  INTEGER :: k, N
223  integer, optional :: n_cachesubsample
224  logical, optional :: init
225  real(kind=dp), optional :: initseq(:)
226
227  if (present(n_cachesubsample)) mc % CacheSubSample = n_cachesubsample
228
229  mc % ABCParams => ABCParams
230  ! allocate(mc)
231  allocate(mc % children(1:STACKSIZEINCREMENT))
232
233  DO k = 2,UBOUND(mc % children,1)
234    mc % children(k) % parent => mc % children(k-1)
235    mc % children(k) % bigloop => bigloop
236  END DO
237  mc % children(1) % bigloop => bigloop
238  allocate(mc % rc_asc, mc % rc_desc)
239  mc % children(1) % parent => mc % rc_desc
240
241  rca => mc % rc_asc
242  rcd => mc % rc_desc
243  rca % bigloop => bigloop
244  rcd % bigloop => bigloop
245
246  N = size(rca % bigloop % BHAsc,1)
247
248  rcd % Bp = rcd % bigloop % BHdesc(N,1)
249  rcd % Bq = rcd % bigloop % BHasc(1,1)
250  rcd % depth = 0
251  rcd % parent => rca
252  rcd % simple_eval => SimpleEvalDescendingSpline
253
254  rca % Bp = rca % bigloop % BHasc(1,1)
255  rca % Bq = rca % bigloop % BHdesc(N,1)
256  rca % depth = 0
257  rca % parent => rcd
258  rca % simple_eval => SimpleEvalAscendingSpline
259
260  mc % head => rcd
261
262  if (present(init) .or. present(initseq)) then
263    if (init .or. present(initseq)) then
264      if (.not. present(initseq)) then ! {{{
265        block
266          integer, parameter :: nr = 12
267          real :: up
268          up = rcd % bigloop % bhasc(N,1)
269          do n=0,nr-2 ! nr-1
270            call mc % drive( up * ((-0.5_dp)**n), cache=.false.)
271          end do
272          n = nr-1
273          call mc % drive( up * ((-0.5_dp)**n), cache=.true.)
274        end block
275      else
276        do n=1,size(initseq)-1
277          call mc % drive(initseq(n), cache = .false.)
278        end do
279        n = size(initseq)
280        call mc % drive(initseq(n), cache = .true.)
281      end if ! }}}
282    end if
283  end if
284
285  if (present(b0)) then
286    mc % b0(1:3) = b0(1:3)
287  else
288    mc % b0 = [0.0_dp, 1.0_dp, 0.0_dp]
289  end if
290
291END function !  }}}
292
293
294
295! Returns curve where B can be evaluated. On saturation, returns the descending (positive saturation) or ascending (negative
296! saturation).
297!
298RECURSIVE FUNCTION RecurseDepth(rc, B) result (rc_p)! {{{
299  IMPLICIT NONE
300  CLASS(RevCurve_t), POINTER, INTENT(IN) :: rc
301  CLASS(RevCurve_t), POINTER :: rc_p
302  REAL(kind=dp) :: B
303  integer :: tmp
304
305#if DEBUG>1
306  integer :: branch
307  branch = -1
308#endif
309
310  tmp = rc % depth
311  ! print *, 'recursedepth: depth, B, Bp, Bq', tmp, B, rc % Bp, rc % Bq
312  outer: block
313    if (rc % Bp < rc % Bq) then ! Ascending curve (here be dragons)
314
315    if (rc % Bp < B .and. B < rc % Bq) THEN
316      rc_p => rc
317#if DEBUG>1
318      branch = 1
319      print *, 'branch: ', branch, rc_p % depth
320#endif
321      exit outer
322    end if
323    if (rc % Bq <= B) then
324
325      if (rc % depth == 0 .and. Ascending(rc)) then ! in positive saturation return descending master
326        rc_p => rc % parent
327#if DEBUG>1
328        branch = 2
329        print *, 'branch: ', branch, rc_p % depth
330#endif
331        exit outer
332      end if
333#if DEBUG>1
334      branch = 3
335      print *, 'branch: ', branch
336#endif
337      rc_p => RecurseDepth(rc % parent % parent, B) ! TODO: Think these through again
338      exit outer
339    end if
340    if (rc % Bp >= B) then
341      if (rc % depth == 0 .and. ascending(rc)) then ! in negative saturation return ascending master
342        rc_p => rc
343#if DEBUG>1
344        branch = 4
345        print *, 'branch: ', branch, rc_p % depth
346#endif
347        exit outer ! Negative saturation
348      end if
349#if DEBUG>1
350      branch = 5
351      print *, 'branch: ', branch
352#endif
353      rc_p => RecurseDepth(rc % parent, B)
354      exit outer
355    end if
356  else ! Descending curve : rc % Bp > rc % Bq
357    if (rc % Bp > B .and. B > rc % Bq) then ! inside
358      rc_p => rc
359#if DEBUG>1
360      branch = 6
361        print *, 'branch: ', branch, rc_p % depth
362#endif
363      exit outer
364    end if
365    if (B <= rc%Bq) then
366      if (rc % depth == 0 .and. descending(rc)) then ! in negative saturation return ascending master
367        rc_p => rc % parent
368#if DEBUG>1
369        branch = 7
370        print *, 'branch: ', branch, rc_p % depth
371#endif
372        exit outer
373      end if
374#if DEBUG>1
375      branch = 8
376      print *, 'branch: ', branch
377#endif
378      rc_p => RecurseDepth(rc % parent % parent, B)
379    end if
380    if (B >= rc % Bp) then
381      if (rc % depth == 0 .and. descending(rc)) then ! in positive saturation return descending master
382        rc_p => rc
383#if DEBUG>1
384        branch = 9
385        print *, 'branch: ', branch, rc_p % depth
386#endif
387        exit outer
388      end if
389#if DEBUG>1
390      branch = 10
391      print *, 'branch: ', branch
392#endif
393      rc_p => RecurseDepth(rc % parent, B)
394      exit outer
395    end if
396  end if
397  end block outer
398
399END FUNCTION ! }}}
400
401SUBROUTINE EvalSplineLoop(this, B, Hasc, Hdesc, dHasc, dHdesc) ! {{{
402  IMPLICIT NONE
403  CLASS(SplineLoop_t), INTENT(IN) :: this
404  REAL(KIND=dp), INTENT(IN) :: B
405  REAL(KIND=dp), INTENT(OUT) :: Hasc, Hdesc
406  REAL(KIND=dp), INTENT(OUT), optional :: dHasc, dHdesc
407
408  if (this % newmethod) then
409    if (B < this % blimits(1) .or. B > this % blimits(2)) then ! negative saturation
410      call this % eval(B, Hasc)
411      Hdesc = Hasc
412      return
413    end if
414  end if
415
416  Hasc = InterpolateCurve( &
417      this%BHasc(:,1),  &
418      this%BHasc(:,2), &
419      B, this%r_asc)
420
421  Hdesc = InterpolateCurve( &
422      this%BHdesc(:,1),  &
423      this%BHdesc(:,2), &
424      B, this%r_desc)
425
426  if (present(dHasc)) then
427    dHasc = DerivateCurve( &
428      this%BHasc(:,1),  &
429      this%BHasc(:,2), &
430      B, this%r_asc)
431  end if
432
433  if (present(dHdesc)) then
434  Hdesc = DerivateCurve( &
435      this%BHdesc(:,1),  &
436      this%BHdesc(:,2), &
437      B, this%r_desc)
438  end if
439
440
441END SUBROUTINE EvalSplineLoop ! }}}
442
443SUBROUTINE EvalSplineLoopSingle(this, B, HSingle) ! {{{
444  IMPLICIT NONE
445  CLASS(SplineLoop_t), INTENT(IN) :: this
446  REAL(KIND=dp), VALUE :: B
447  REAL(KIND=dp), INTENT(OUT) :: HSingle
448  logical :: neg
449
450  neg = B < 0
451  if (neg) B = -B
452
453  HSingle = InterpolateCurve( &
454      this % BHsat(:,1), &
455      this % BHsat(:,2), &
456      B, this % r_sat)
457
458  if (neg) Hsingle = -Hsingle
459
460END SUBROUTINE EvalSplineLoopSingle ! }}}
461
462! Evaluate rc at B. Don't check if B is consistent with rc%Bp and rc%Bq
463RECURSIVE FUNCTION SimpleEvalRevCurve(rc, B) result(H) ! {{{
464  CLASS(RevCurve_t), INTENT(IN), target :: rc
465  REAL(KIND=dp), INTENT(IN) :: B
466  REAL(KIND=dp) :: H
467  !-------------------------------------------------------------------------------
468  REAL(KIND=dp) :: Hap,  Hp, x, dHout, dH, HMAsc, HMDesc
469  !-------------------------------------------------------------------------------
470#if 0
471  depthtest: if (rc%depth > 2) then
472    Hp = rc % parent % simple_eval(B)
473    Hap = rc % parent % parent % simple_eval(B)
474  else
475    call rc % bigloop % eval(B, HMAsc, HMDesc)
476    IF (rc % depth == 2) then
477      Hap = HMDesc
478      Hp = rc % parent % simple_eval(B)
479      exit depthtest
480    end if
481    IF (rc % depth == 1 ) then
482      Hap = HMAsc
483      Hp = HMDesc
484      exit depthtest
485    END IF
486    IF (rc % depth == 0) H = HMDesc
487    IF (rc % depth == -1) H = HMAsc
488  endif depthtest
489#else
490  Hp = rc % parent % simple_eval(B)
491  Hap = rc % parent % parent % simple_eval(B)
492#endif
493  if (rc % depth > 0) then
494    x = (rc % Bq - B )/rc % dBrev
495    dHout = Hap - Hp
496    dH = rc % dHrev*(1.0_dp-rc % b) * x * exp(-rc%a*(1.0_dp-x)) + dHout* rc % b* (x**rc%c)
497    H = Hap - dH
498  end if
499END FUNCTION ! }}}
500
501RECURSIVE FUNCTION SimpleEvalAscendingSpline(rc, B) result(H) ! {{{
502  CLASS(Revcurve_t), INTENT(in), target :: rc
503  REAL(KIND=dp), intent(in) :: B
504  REAL(KIND=dp) :: H, Hdesc
505  call rc % bigloop % eval(B, H, Hdesc)
506END FUNCTION ! }}}
507
508RECURSIVE FUNCTION SimpleEvalDescendingSpline(rc, B) result(H) ! {{{
509  CLASS(Revcurve_t), INTENT(in), target :: rc
510  REAL(KIND=dp), intent(in) :: B
511  REAL(KIND=dp) :: H, Hasc
512  call rc % bigloop % eval(B, Hasc, H)
513END FUNCTION ! }}}
514
515! Evaluate master curve at B,
516function mc_eval(mc, B, dhdb, cached) result(H) ! {{{
517  class(MasterCurve_t) :: mc
518  real(kind=dp), intent(in) :: B
519  real(kind=dp) :: H
520  real(kind=dp), optional, intent(out) :: dhdb
521  real(kind=dp) :: dhasc, dhdesc, hasc, hdesc
522  logical, optional :: cached
523
524  if (present(cached) .and. cached ) then
525      H = InterpolateCurve( &
526          mc % BH(:,1), &
527          mc % BH(:,2), &
528          B, mc % r_BH)
529    if (present(dhdb)) then
530      dhdb = DerivateCurve( &
531          mc % BH(:,1), &
532          mc % BH(:,2), &
533          B, mc % r_BH)
534    end if
535  else
536      H = RecurEvalCurve(mc % head, B)
537      if(present(dhdb)) dhdb = 0.0_dp
538  end if
539end function ! }}}
540
541FUNCTION RecurEvalCurve(rc, B) result (H) ! {{{
542  IMPLICIT NONE
543  CLASS(RevCurve_t), TARGET,  INTENT(IN) :: rc
544  REAL(KIND=dp), INTENT(IN) :: B
545  CLASS(RevCurve_t), POINTER :: rc_p
546  real(kind=dp) :: H
547  integer :: d
548
549  rc_p => rc
550  d = rc_p % depth
551  rc_p => RecurseDepth(rc_p, B)
552  H = rc_p % simple_eval(B)
553
554END FUNCTION ! }}}
555
556SUBROUTINE HBDrive(mc, B, cache) ! {{{
557  IMPLICIT NONE
558  CLASS(MasterCurve_t), INTENT(INOUT) :: mc
559  CLASS(RevCurve_t), POINTER :: rc
560  REAL(KIND=dp), INTENT(IN) :: B
561  !-------------------------------------------------------------------------------
562  CLASS(RevCurve_t), POINTER :: rc_p
563  integer :: m, n, n_cache
564  logical, optional :: cache
565  logical :: cache_
566  real(kind=dp) :: bmax, bmin
567  !-------------------------------------------------------------------------------
568
569#if DEBUG>1
570  print *, 'hbdrive driving stack to ', B
571#endif
572  rc_p => RecurseDepth(mc % head, B)
573  call AddStack(rc_p, mc, B)
574  rc => rc_p
575
576  if (.not. present(cache)) then
577    cache_ = .true.
578  else
579    cache_ = cache
580  end if
581
582    if(cache_) then
583#if DEBUG>1
584      print *, rc % bigloop % blimits(1:2)
585#endif
586      n = size(rc % bigloop % bhsat,1)
587      bmax = rc % bigloop % bhsat(n,1)
588      bmin = -rc % bigloop % bhsat(n,1)
589      n_cache = size(rc % bigloop % bhsat,1) / mc % cachesubsample
590
591      IF(.not. ALLOCATED(mc % BH)) ALLOCATE(mc % BH(n_cache,2))
592      IF(.not. associated(mc % r_bh)) ALLOCATE(mc % r_bh(n_cache))
593
594      DO n = 1, size(mc % BH,1)
595        associate( Bhere => (bmin + (n-1)*(bmax-bmin)/n_cache ) )
596          mc % BH(n,1) = Bhere
597          mc % BH(n,2) = mc % eval(Bhere, cached =.false.)
598        end associate
599      END DO
600      n = size(mc % bh,1)
601      CALL CubicSpline(n, mc % BH(:,1), mc % BH(:,2), mc % r_bh, monotone=.true.)
602  end if
603
604END SUBROUTINE  !}}}
605
606subroutine mc_addstack(mc, B) ! {{{
607  class(MasterCurve_t) :: mc
608  real(kind=dp), intent(in) :: B
609
610  call addstack(mc % head, mc, B)
611end subroutine ! }}}
612
613subroutine mc_printme(mc) ! {{{
614  class(MasterCurve_t) :: mc
615  call PrintRevCurve(mc % head)
616end subroutine ! }}}
617
618SUBROUTINE AddStack(parent, master, B) ! {{{
619  IMPLICIT NONE
620  CLASS (RevCurve_t), INTENT(INOUT), POINTER :: parent
621  TYPE(MasterCurve_t), INTENT(INOUT) :: master
622  CLASS(RevCurve_t), POINTER:: x
623  REAL(KIND=dp), INTENT(IN) :: B
624  !-------------------------------------------------------------------------------
625  real(KIND=dp) :: HMAsc, HMDesc, Hpp, hp, dBout
626  integer :: d, d_add
627
628  ! TODO: This looks weird
629  ! print *, 'recurse: ', parent % depth
630  ! Check for saturation
631#if 0
632  if (descending(parent) .and. B > parent % Bp) then ! depth == 0 => descending outer
633    master % head => parent
634    return
635  end if
636  if (ascending(parent)  .and. B < parent % Bp) then ! depth == -1 => ascending outer
637    master % head => parent
638    return
639  end if
640#else
641  if (descending(parent)) then ! descending master
642    if ( B <= parent % Bq ) then ! in negative saturation return ascending master
643      master % head => parent % parent
644      return
645    end if
646    if ( B > parent % Bp ) then ! in positive saturation return descending master
647      master % head => parent
648      return
649    end if
650  end if
651  if (ascending(parent)) then ! ascending master
652    if ( B >= parent % Bq ) then !  in positive saturation return descending master
653      master % head => parent % parent
654      return
655    end if
656    if ( B < parent % Bp ) then ! in negative saturation return ascending master
657      master % head => parent
658      return
659    end if
660  end if
661#endif
662
663
664  ! Not in saturation
665  parent => check_reallocate(parent, parent % depth + 1)
666  x => master % children(parent % depth + 1)
667  x % depth = parent % depth + 1
668
669  ! parent => master % children ( parent % depth + d_add - 1)
670  x % parent => parent
671  x % Bp = B
672  x % Bq = x % parent % Bp
673  x % dBrev = x%Bq - x%Bp
674  dBout = x % Bq - parent % parent % Bp
675  call master % ABCparams % GetABC(abs(dBout), abs(x % dBrev), x%a, x%b, x%c)
676  Hpp = x % parent % parent % simple_eval(B)
677  Hp = x % parent % simple_eval(B)
678  x % dHrev = Hpp - Hp;
679  ! parent => x
680  master % head  => x
681
682
683  CONTAINS
684  function check_reallocate(oldparent, newdepth) result(newparent) ! {{{
685    IMPLICIT NONE
686    INTEGER :: newdepth
687    !-------------------------------------------------------------------------------
688    TYPE(Revcurve_t), POINTER :: newchildren(:)
689    ! type(Revcurve_t), pointer :: x
690    TYPE(SplineLoop_t), POINTER :: bigloop
691    INTEGER :: bufbound, k
692    CLASS(RevCurve_t), POINTER :: oldparent, newparent
693    !-------------------------------------------------------------------------------
694    bufbound = ubound(master % children, 1)
695    ! bigloop => master % children(1) % bigloop
696    bigloop => oldparent % bigloop
697    ! nullify(parent)
698
699    if (newdepth > 1) THEN
700      IF (newdepth > bufbound) THEN
701        allocate(newchildren(1:bufbound + STACKSIZEINCREMENT))
702        newchildren(1:bufbound) = master % children(1:bufbound)
703        do k =1,bufbound
704          nullify(master % children(k) % bigloop)
705        end do
706        nullify(master % head)
707
708        deallocate(master % children)
709        allocate(master % children(1:bufbound + STACKSIZEINCREMENT), source=newchildren)
710        do k =1,newdepth
711          master % children(k) % bigloop => bigloop
712        end do
713
714        master % head => master % children(newdepth)
715      END IF
716      master % children(newdepth) % bigloop => bigloop
717      master % head => master % children(newdepth)
718      do k = 2, newdepth
719        master % children(k) % parent => master % children(k-1)
720      end do
721      newparent => master % children(newdepth-1)
722      ! master % children(-1) % parent => master % children(0)
723    else
724      newparent => oldparent
725    end if
726
727
728  END function !}}}
729END SUBROUTINE AddStack ! }}}
730
731subroutine mc_printeval(mc, B, mc2) ! {{{
732  class(MasterCurve_t) :: mc
733  class(MasterCurve_t), optional:: mc2
734  real(kind=dp) :: B
735  if (present(mc2)) then
736    call printeval(mc % head, B, mc2 % head)
737  else
738    call printeval(mc % head, B)
739  end if
740
741end subroutine ! }}}
742
743SUBROUTINE rc_printeval(rc, B, rc0) ! {{{
744  implicit none
745  class(revcurve_t), pointer, intent(in) :: rc
746  class(revcurve_t), pointer, intent(in), optional :: rc0
747  real(kind=dp), intent(in) :: B
748  real(kind=dp) :: X, X0
749  class(revcurve_t), pointer :: rc_p, rc0_p
750  integer :: k
751  rc_p => rc
752  if (present(rc0)) rc0_p => rc0
753  k = rc_p % depth
754  do while (.not. associated(rc_p, rc_p % parent % parent))
755    if(present(rc0)) X0 = rc0_p % simple_eval(B)
756    X = rc_p % simple_eval(B)
757    if (present(rc0)) print *, X, X0, X-X0
758    if (.not. present(rc0)) print *, X, rc_p % depth ! , c_loc(rc_p), c_loc(rc_p % parent)
759    rc_p => rc_p % parent
760    if(present(rc0)) rc0_p => rc0_p % parent
761  end do
762  X = rc_p % simple_eval(B)
763  if(present(rc0)) X0 = rc0_p % simple_eval(B)
764  if (present(rc0)) print *, X, X0, X-X0
765  if (.not. present(rc0)) print *, X, rc_p % depth ! , c_loc(rc_p), c_loc(rc_p % parent)
766END SUBROUTINE rc_printeval ! }}}
767! Debugging purposes only
768subroutine PrintRevCurve(rc0) ! {{{
769  CLASS(RevCurve_t), POINTER, intent(in) :: rc0
770  CLASS(RevCurve_t), POINTER :: rc
771
772  rc=>rc0
773
774  write (*,*) 'current depth:', rc0 % depth
775  do while (.not. associated(rc%parent % parent, rc) .and. associated(rc))
776    write (*,*) 'depth, Bp, Bq, dBrev, dHrev:', rc%depth, rc%Bp, rc%Bq, rc % dBrev, rc % dHrev
777    rc => rc % parent
778  end do
779  write (*,*) 'depth, Bp, Bq, dBrev, dHrev:', rc%depth, rc%Bp, rc%Bq, rc % dBrev, rc % dHrev
780  rc => rc % parent
781  write (*,*) 'depth, Bp, Bq, dBrev, dHrev:', rc%depth, rc%Bp, rc%Bq, rc % dBrev, rc % dHrev
782  rc => rc % parent
783end subroutine ! }}}
784
785!-------------------------------------------------------------------------------
786SUBROUTINE FreeRevCurve(mc) ! {{{
787!-------------------------------------------------------------------------------
788  implicit none
789  TYPE(MasterCurve_t) :: mc
790!-------------------------------------------------------------------------------
791  DEALLOCATE(mc % children)
792END SUBROUTINE ! }}}
793!-------------------------------------------------------------------------------
794
795END MODULE ! }}}
796
797module ZirkaUtils ! Utils for 2D/3D calculations {{{
798use zirka
799use DefUtils
800
801character(len=*), parameter :: default_zirka_variable_name = 'zirka_ipvar'
802
803contains
804!-------------------------------------------------------------------------------
805! Initialization for 2D case
806!-------------------------------------------------------------------------------
807SUBROUTINE InitHysteresis(Model,Solver) ! {{{
808!-------------------------------------------------------------------------------
809  IMPLICIT NONE
810  TYPE(Model_t) :: Model
811  TYPE(Solver_t) :: Solver
812!-------------------------------------------------------------------------------
813  integer :: elemind, t, ipindex, n, nd
814  TYPE(GaussIntegrationPoints_t) :: IP
815  TYPE(SplineLoop_t), POINTER :: bigloop
816  REAL(kind=dp), POINTER :: BHasc(:,:), BHsingle(:,:), initseq(:,:)
817  TYPE(Nodes_t) :: Nodes
818  TYPE(Element_t), POINTER::Element
819  type(ValueList_t), pointer :: material
820  type(ValueListEntry_t), pointer :: ZirkaEntry
821  INTEGER, PARAMETER :: n_dir_default = 8
822  logical :: found, zeroinit, HasZirka
823  integer :: n_dir, n_zirka_mat, n_initialized, n_cachesubsample
824  CHARACTER(len=MAX_NAME_LEN) :: str
825  type(GlobalHysteresisModel_t), POINTER :: zirkamodel
826  type(Variable_t), POINTER :: hystvar
827  type(ZirkaABC_t), POINTER :: ABCParams
828  real(kind=dp), POINTER :: zcoeff(:,:)
829  real(kind=dp) :: b_mult, c_mult
830!-------------------------------------------------------------------------------
831  n_zirka_mat = 0
832  n_initialized = 0
833
834
835  do n = 1,model % NumberOfMaterials ! {{{
836    material => model % materials(n) % values
837    ZirkaEntry => listfind(material, 'zirka material', haszirka)
838    if (.not. HasZirka) CYCLE
839    if (.not. ZirkaEntry % LValue) CYCLE
840    n_zirka_mat = n_zirka_mat + 1
841
842    if(ListGetLogical(material, 'zirka initialized', HasZirka)) then
843      n_initialized = n_initialized + 1
844      CYCLE
845    end if
846
847    zirkamodel => NULL()
848    BHasc => NULL()
849    BHSingle => NULL()
850
851    hystvar => GetZirkaVariable(Material)
852
853    if (.not. associated(hystvar)) HystVar => CreateZirkaVariable(Material)
854    ! call fatal('InitHysteresis', 'hystvar > ' // trim(str) //' < not available')
855
856    n_dir = ListGetInteger(material, 'Zirka Directions', found)
857    if (.NOT. found) n_dir = ListGetInteger(material, 'n_dir', found)
858    IF(.NOT. Found) n_dir = n_dir_default
859
860    zeroinit = ListGetLogical(material, 'Init to zero', zeroinit)
861
862    ipindex = GetIpCount(usolver=solver, ipvar=hystvar)
863    allocate(zirkamodel)
864    allocate(zirkamodel % curves(n_dir, ipindex))
865    ZirkaEntry % PROCEDURE = TRANSFER(c_loc(zirkamodel), ZirkaEntry % PROCEDURE)
866
867    ! Initialize saturation loops and saturation curve
868    call GetConstRealArray(Material, BHasc, 'Ascending BH curve', found)
869    if(.not. Found) call fatal('InitHysteresis', 'Ascending BH curve not found')
870    call GetConstRealArray(Material, BHSingle, 'Single valued BH curve', found)
871    if(.not. Found) call fatal('InitHysteresis', 'Single valued BH curve not found')
872
873    zirkamodel % masterloop => InitSplineLoop(BHasc, BHSingle)
874    zirkamodel % CacheSubSample = ListGetInteger(Material, 'Zirka Spline Cache Subsample', Found)
875    if(.not. Found) Zirkamodel % CacheSubSample = 2
876
877    ! Set up zirka ABC data here
878    allocate(ABCParams)
879    zirkamodel % ABCParams => ABCParams
880    call GetConstRealArray(Material,  zcoeff, 'Zirka model coefficients', found)
881    if(Found .and. size(zcoeff,1) == 4) then
882      ABCparams % coeffs(1:4) = zcoeff(1:4,1)
883    end if
884
885    b_mult = GetCReal(Material, 'Zirka model b multiplier', found)
886    if(found) zirkamodel % abcparams % b_mult = b_mult
887
888    c_mult = GetCReal(Material, 'Zirka model c multiplier', found)
889    if(found) zirkamodel % abcparams % c_mult = c_mult
890  end do ! }}}
891
892  if (.not. n_zirka_mat == n_initialized) then ! only init if some
893    do elemind = 1,getnofactive() ! {{{
894      element => GetActiveElement(elemind)
895
896      material => GetMaterial()
897      ZirkaEntry => ListFind(material, 'zirka material', haszirka)
898      if (.not. HasZirka) CYCLE
899
900      initseq => null()
901      call GetConstRealArray(Material, initseq, 'zirka init sequence', found)
902      if(.not. found) initseq => null()
903
904      if(ListGetLogical(material, 'zirka initialized', HasZirka)) CYCLE
905
906      IP = GaussPoints(element)
907
908      inithystblock: block ! {{{
909        REAL(KIND=dp) :: phi
910        integer :: i
911
912        zirkamodel => GetZirkaPointer(material)
913
914        if(.not. associated(zirkamodel)) then
915          print *, 'zirkamodel is not associated, cycling' ! DEBUG
916          cycle
917        end if
918
919        do t = 1, ip%N
920          ipindex = getipindex(t, usolver=solver, element=element, ipvar=hystvar)
921          if (ipindex == 0) CYCLE
922          do i = 1,n_dir
923            phi = (i-1.0)*pi/n_dir
924            ! print *, 'hello', i, t, ipindex ! DEBUG
925            if(.not. associated(initseq)) then
926              zirkamodel % curves(i, ipindex) = &
927                  MasterCurve_t(zirkamodel % masterloop, ABCparams, &
928                  init=zeroinit, b0=[sin(phi), cos(phi), 0.0_dp], &
929                  n_cachesubsample = zirkamodel % CacheSubSample)
930            else
931              zirkamodel % curves(i, ipindex) = &
932                  MasterCurve_t(zirkamodel % masterloop, ABCparams, &
933                  init=.true., b0=[sin(phi), cos(phi), 0.0_dp], initseq=initseq(:,1), &
934                  n_cachesubsample = zirkamodel % CacheSubSample)
935            end if
936          end do
937        end do
938      end block  inithystblock ! }}}
939
940    end do ! }}}
941  end if
942
943  ! set initialized to true
944  do n = 1,model % NumberOfMaterials ! {{{
945    Material => Model % Materials(n) % Values
946    ZirkaEntry => ListFind(material, 'zirka material', haszirka)
947    if (.not. HasZirka) CYCLE
948
949    if(ListGetLogical(material, 'zirka initialized', HasZirka)) cycle
950    call ListAddLogical(material, 'zirka initialized', .true.)
951  end do ! }}}
952  write(message, '(A,I0,A,I0)') 'Found ',n_zirka_mat,' Zirka hysteresis models and skipped initialization of ', n_initialized
953  call Info('InitHysteresis',message,level=10)
954
955!-------------------------------------------------------------------------------
956END SUBROUTINE InitHysteresis ! }}}
957!-------------------------------------------------------------------------------
958
959!-------------------------------------------------------------------------------
960! Returns a pointer to the object containing hysteresis model of given material
961!-------------------------------------------------------------------------------
962FUNCTION GetZirkaPointer(material) result(ZirkaModelPtr) ! {{{
963!-------------------------------------------------------------------------------
964  TYPE(ValueList_t), POINTER, intent(in) :: Material
965  TYPE(GlobalHysteresisModel_t), POINTER :: ZirkaModelPtr
966!-------------------------------------------------------------------------------
967  TYPE(ValueListEntry_t), POINTER :: ZirkaEntry
968  TYPE(C_PTR) :: c_zirka_ptr
969  logical :: haszirka
970
971  ZirkaModelPtr => Null()
972  ZirkaEntry => ListFind(material, 'zirka material', haszirka)
973  IF(.NOT. HasZirka) RETURN
974
975  c_zirka_ptr = transfer(ZirkaEntry % PROCEDURE, c_zirka_ptr)
976  call C_F_POINTER(c_zirka_ptr, ZirkaModelPtr)
977END FUNCTION ! }}}
978!-------------------------------------------------------------------------------
979
980FUNCTION CreateZirkaVariable(Material) RESULT(var)
981!-------------------------------------------------------------------------------
982  USE MainUtils
983  IMPLICIT NONE
984  TYPE(ValueList_t), POINTER, intent(in) :: Material
985  TYPE(Variable_t), POINTER :: var
986!-------------------------------------------------------------------------------
987  CHARACTER(len=MAX_NAME_LEN) :: str
988  type(mesh_t), pointer :: mesh
989  logical :: found
990
991  mesh => getmesh()
992
993  str = ListGetString(material, 'Zirka variable', found)
994  if(.not. found) str = default_zirka_variable_name !'zirka'
995  var => VariableGet(mesh % variables, str)
996  if(associated(var)) THEN
997    write (Message, '(A)') 'Attempting to create existing variable > ' // trim(str) // ' <'
998    call warn('CreateZirkaVariable', Message)
999    return
1000  END IF
1001  BLOCK
1002    LOGICAL :: HasZirka, Found
1003    TYPE(ValueListEntry_t), POINTER :: zmat
1004    CHARACTER(len=MAX_NAME_LEN) :: maskname, mask_sec
1005    INTEGER, POINTER :: Perm(:)
1006    REAL(KIND=dp), POINTER :: Values(:)
1007    type(Solver_t), POINTER :: PSolver
1008    type(Model_t) :: Model
1009    integer :: nsize
1010
1011
1012    PSolver => GetSolver()
1013    maskname = 'zirka material'
1014    mask_sec = 'material'
1015    zmat => ListFind( material , 'Zirka Material' , Found)
1016    IF (Found .and. zmat % LValue) THEN
1017      call CreateIpPerm(PSolver, Perm, maskname, mask_sec)
1018      nsize = maxval(perm)
1019      allocate(Values(nsize))
1020      call VariableAdd( PSolver % Mesh % Variables, PSolver % Mesh, PSolver, &
1021          trim(str), 1, Values, Perm, output = .TRUE., TYPE=Variable_on_gauss_points)
1022    END IF
1023  END BLOCK
1024  var => VariableGet(mesh % variables, str)
1025END FUNCTION CreateZirkaVariable
1026
1027!-------------------------------------------------------------------------------
1028! Returns a pointer to the variable holding hysteresis models
1029!-------------------------------------------------------------------------------
1030FUNCTION GetZirkaVariable(Material) RESULT(ZirkaVariable) ! {{{
1031!-------------------------------------------------------------------------------
1032  TYPE(ValueList_t), POINTER, intent(in) :: Material
1033  TYPE(Variable_t), POINTER :: ZirkaVariable
1034!-------------------------------------------------------------------------------
1035  CHARACTER(len=MAX_NAME_LEN) :: str
1036  type(mesh_t), pointer :: mesh
1037  logical :: found
1038
1039  mesh => getmesh()
1040
1041  str = ListGetString(material, 'Zirka variable', found)
1042  if(.not. found) str = default_zirka_variable_name! 'zirka'
1043  ZirkaVariable => VariableGet(mesh % variables, str)
1044  if(associated(zirkaVariable)) return
1045
1046!-------------------------------------------------------------------------------
1047END FUNCTION ! }}}
1048!-------------------------------------------------------------------------------
1049
1050!-------------------------------------------------------------------------------
1051! Sets the hysteresis state globally in 2D case
1052!-------------------------------------------------------------------------------
1053SUBROUTINE DriveHysteresis(model, solver) ! {{{
1054!-------------------------------------------------------------------------------
1055  IMPLICIT NONE
1056  type(model_t) :: model
1057  type(solver_t) :: solver
1058!-------------------------------------------------------------------------------
1059  integer :: elemind, ipindex, n, nd
1060  TYPE(GaussIntegrationPoints_t) :: IP
1061  ! type(RevCurve_t), POINTER :: rc
1062  TYPE(SplineLoop_t), POINTER :: bigloop
1063  REAL(kind=dp) :: H(1:5), Basc(1:5), Bdesc(1:5)
1064  TYPE(Nodes_t) :: Nodes
1065  TYPE(Element_t), POINTER::Element
1066  TYPE(Variable_t), POINTER :: hystvar
1067  TYPE(GlobalHysteresisModel_t), pointer :: zirkamodel
1068  logical :: found
1069!-------------------------------------------------------------------------------
1070#if DEBUG>0
1071  integer :: printed
1072  printed =  0
1073#endif
1074
1075  do elemind = 1,getnofactive()
1076    element => GetActiveElement(elemind)
1077
1078    n  = GetElementNOFNodes(Element)
1079    nd = GetElementNOFDOFs(Element)
1080    IP = GaussPoints(element)
1081    if (.not. ListGetLogical(GetMaterial(Element), 'zirka material',found)) CYCLE
1082    zirkamodel => GetZirkaPointer(GetMaterial(Element))
1083    hystvar => GetZirkaVariable(GetMaterial(Element))
1084
1085
1086drivehystblock: block
1087    REAL(KIND=dp) :: Basis(nd),dBasisdx(nd,3),DetJ, B_ip(3), POT(nd), Agrad(3)
1088    LOGICAL :: einfostat
1089    REAL(kind=dp), parameter :: zstab=1.0e-5
1090    integer :: counter, t, n_dir
1091
1092    CALL GetLocalSolution(POT,UElement=Element,USolver=Solver)
1093    CALL GetElementNodes(Nodes)
1094
1095    counter = 0
1096    do t = 1, ip%N
1097      ipindex = getipindex(t, usolver=solver, element=element, ipvar=hystvar)
1098      if (ipindex == 0) cycle
1099#if DEBUG>0
1100      if (printed == 0) then ! DEBUG
1101        call zirkamodel % curves(1,ipindex) % printme()
1102        printed = printed + 1
1103      end if
1104#endif
1105
1106      counter = counter + 1
1107      einfostat = ElementInfo( &
1108          Element, Nodes, IP % U(t), IP % V(t), &
1109          IP % W(t), detJ, Basis, dBasisdx )
1110      ! B_ip(1) = -SUM(POT*dBasisdx(:,2))
1111      ! B_ip(2) = SUM(POT*dBasisdx(:,1))
1112      ! B_ip(3) = 0.0_dp
1113
1114      Agrad = matmul(POT, dbasisdx)
1115      B_ip(1) = Agrad(2)
1116      B_ip(2) = -Agrad(1)
1117      b_ip(3) = 0.0_dp
1118#if DEBUG>0
1119      if (printed < 2) then ! DEBUG
1120        print *, 'B_ip = ', B_ip, printed
1121        printed = printed + 1
1122      end if
1123#endif
1124
1125      do n_dir = 1, ubound(zirkamodel % curves, 1)
1126        call zirkamodel % curves(n_dir, ipindex) % &
1127            drive(sum(zirkamodel % curves(n_dir, ipindex) % B0 * B_ip))
1128      end do
1129#if DEBUG>0
1130      if (printed < 3) then ! DEBUG
1131        call zirkamodel % curves(1,ipindex) % printme()
1132        printed = printed + 1
1133      end if
1134#endif
1135    end do
1136
1137end block drivehystblock
1138
1139  end do
1140!-------------------------------------------------------------------------------
1141END SUBROUTINE DriveHysteresis ! }}}
1142!-------------------------------------------------------------------------------
1143
1144!-------------------------------------------------------------------------------
1145! Add magnetic field strength to long vector force used in MagnetoDynamicsCalcFields
1146!-------------------------------------------------------------------------------
1147SUBROUTINE GetHystereticMFS(Element, Force, pSolver, HasZirka, CSymmetry) ! {{{
1148!-------------------------------------------------------------------------------
1149  use DefUtils
1150  type(element_t), intent(in) :: element
1151  real(kind=dp), intent(inout) :: force(:,:)
1152  type(solver_t), intent(in) :: pSolver
1153  logical, intent(out) :: HasZirka
1154  LOGICAL, OPTIONAL :: CSymmetry
1155!-------------------------------------------------------------------------------
1156  type(ValueList_t), pointer :: material
1157
1158  LOGICAL :: einfostat
1159  integer :: n, nd, t, n_dir
1160  type(GaussIntegrationPoints_t) :: IP
1161  type(GlobalHysteresisModel_t), pointer :: zirkamodel
1162  type(variable_t), pointer :: hystvar
1163  type(nodes_t) :: nodes
1164  LOGICAL :: CSymmetry_
1165
1166  IF(.NOT. present(CSymmetry)) THEN
1167    CSymmetry_ = .FALSE.
1168  ELSE
1169    CSymmetry_ = CSymmetry
1170  END IF
1171
1172  Material => GetMaterial(Element)
1173  HasZirka = ListGetLogical(Material, 'zirka material', HasZirka)
1174  if (.not. HasZirka) RETURN
1175  zirkamodel => GetZirkaPointer(GetMaterial(Element))
1176  hystvar => GetZirkaVariable(GetMaterial(Element))
1177
1178  n  = GetElementNOFNodes(Element)
1179  nd = GetElementNOFDOFs(Element)
1180  IP = GaussPoints(element)
1181  block
1182    REAL(KIND=dp) :: Basis(nd),dBasisdx(nd,3),DetJ, B_ip(2), POT(nd),H_ip(2), &
1183        Agrad(3), x, Alocal
1184    integer :: p, ipindex
1185
1186    call getlocalsolution(pot, uelement=element, usolver=psolver)
1187    CALL GetElementNodes(Nodes)
1188
1189    DO t=1,IP % n
1190      ipindex = getipindex(t, usolver=psolver, element=element, ipvar=hystvar)
1191      if (ipindex == 0) cycle
1192      H_ip = 0.0_dp
1193      einfostat = ElementInfo( &
1194          Element, Nodes, IP % U(t), IP % V(t), &
1195          IP % W(t), detJ, Basis, dBasisdx )
1196
1197      ! No symmetries yet
1198      Agrad = matmul(POT, dbasisdx)
1199      B_ip(1) = Agrad(2)
1200      B_ip(2) = -Agrad(1)
1201      IF( CSymmetry ) THEN
1202        Alocal = sum(Pot(1:n)*basis(1:n))
1203        x = SUM( Basis(1:n) * Nodes % x(1:n) )
1204        detJ = detJ * x
1205        B_ip = -B_ip
1206        B_ip(2) = B_ip(2) + Alocal/x
1207      END IF
1208
1209      do n_dir = 1, ubound(zirkamodel % curves, 1)
1210        associate(B0 => zirkamodel % curves(n_dir, ipindex) % B0)
1211          H_ip = H_ip + zirkamodel % curves(n_dir,ipindex) % &
1212              eval(sum(B_ip*B0(1:2)), cached = .true.) * &
1213              B0(1:2)
1214        end associate
1215      end do
1216      do p=1,n
1217        FORCE(p,1:2) = FORCE(p,1:2) + ip%s(t)*detJ*H_ip(1:2)*Basis(p)
1218      end do
1219    end do
1220
1221  end block
1222
1223!-------------------------------------------------------------------------------
1224END SUBROUTINE  ! }}}
1225!-------------------------------------------------------------------------------
1226end module ! }}}
1227
1228