2 !*** Jan Mandel August-October 2007 email: jmandel@ucar.edu or Jan.Mandel@gmail.com
4 ! With contributions by Minjeong Kim.
8 !#define DEBUG_OUT_FUEL_LEFT
10 module module_fr_sfire_core
12 use module_fr_sfire_phys
13 use module_fr_sfire_util
15 ! The mathematical core of the fire spread model. No physical constants here.
17 ! subroutine sfire_core: only this routine should be called from the outside.
18 ! subroutine fuel_left: compute remaining fuel from time of ignition.
19 ! subroutine prop_ls: propagation of curve in normal direction.
24 !****************************************
27 subroutine init_no_fire(&
28 ifds,ifde,jfds,jfde, &
29 ifms,ifme,jfms,jfme, &
30 ifts,ifte,jfts,jfte, &
31 fdx,fdy,time_now, & ! scalars in
32 fuel_frac,fire_area,lfn,tign) ! arrays out
35 !*** purpose: initialize model to no fire
38 integer, intent(in):: ifds,ifde,jfds,jfde ! fire domain bounds
39 integer, intent(in):: ifts,ifte,jfts,jfte ! fire tile bounds
40 integer, intent(in):: ifms,ifme,jfms,jfme ! array bounds
41 real, intent(in) :: fdx,fdy,time_now ! mesh spacing, time
42 real, intent(out), dimension (ifms:ifme,jfms:jfme) :: &
43 fuel_frac,fire_area,lfn,tign ! model state
50 real lfn_init,time_init
52 lfn_init = 2*max((ifde-ifds+1)*fdx,(jfde-jfds+1)*fdy) ! more than domain diameter
53 time_init=time_now + max(time_now,1.0)*epsilon(time_now) ! a bit in future
57 fuel_frac(i,j)=1. ! fuel at start is 1 by definition
58 fire_area(i,j)=0. ! nothing burning
59 tign(i,j) = time_init ! ignition in future
60 lfn(i,j) = lfn_init ! no fire
63 call message('init_model_no_fire: state set to no fire')
65 end subroutine init_no_fire
72 subroutine ignite_fire( ifds,ifde,jfds,jfde, & ! fire domain dims - the whole domain
73 ifms,ifme,jfms,jfme, &
74 ifts,ifte,jfts,jfte, &
75 start_x,start_y,end_x,end_y, &
83 !*** purpose: ignite a circular/line fire
86 ! ignite fire in the region within radius r from the line (sx,sy) to (ex,ey).
87 ! the coordinates of nodes are given as the arrays coord_xf and coord_yf
89 ! one unit of coord_xf is unit_xf m
90 ! one unit of coord_yf is unit_yf m
91 ! so a node (i,j) will be ignited iff for some (x,y) on the line
92 ! || ( (coord_xf(i,j) - x)*unit_xf , (coord_yf(i,j) - y)*unit_yf ) || <= r
96 integer, intent(in):: ifds,ifde,jfds,jfde ! fire domain bounds
97 integer, intent(in):: ifts,ifte,jfts,jfte ! fire tile bounds
98 integer, intent(in):: ifms,ifme,jfms,jfme ! array bounds
99 real, intent(in):: start_x,start_y ! start of ignition line, from lower left corner
100 real, intent(in):: end_x,end_y ! end of ignition line, or zero
101 real, intent(in):: r ! all within the radius of the line will ignite
102 real, intent(in):: start_t,end_t ! the ignition time for the start and the end of the line
103 real, intent(in):: start_ts,end_ts ! the time step start and end
104 real, dimension(ifms:ifme, jfms:jfme), intent(in):: &
105 coord_xf,coord_yf ! node coordinates
106 real, intent(in):: unit_xf,unit_yf ! coordinate units in m
107 real, intent(inout), dimension (ifms:ifme,jfms:jfme) :: &
108 lfn, tign ! level function, ignition time (state)
109 integer, intent(out):: ignited ! number of nodes newly ignited
113 real::mx,my,ax,ay,dam2,d,dames,des2,am_es,cos2,lfn_new,dmc2,time_ign,rels,rele,mid_t,cos_ame,dif_th
114 real:: sx,sy ! start of ignition line, from lower left corner
115 real:: ex,ey ! end of ignition line, or zero
116 real:: st,et ! start and end of time of the ignition line
117 character(len=128):: msg
120 st=max(start_ts,start_t) ! the start time of ignition in this time step
121 et=min(end_ts,end_t) ! the end time of ignition in this time step
123 if(st>et)return ! no ignition in this time step, nothing to do
126 ! find the points where the ignition line starts and ends this time step
127 ! compute in a way that is stable when end_t - start_t is small
128 if(start_t < end_t)then
129 rels = (st - start_t) / (end_t - start_t)
130 sx = start_x + rels * (end_x - start_x)
131 sy = start_y + rels * (end_y - start_y)
132 rele = (et - end_t) / (end_t - start_t)
133 ex = end_x + rele * (end_x - start_x)
134 ey = end_y + rele * (end_y - start_y)
142 mid_t = (end_t + start_t)*0.5 ! ignition time in the middle
143 dif_th = (end_t - start_t)*0.5
150 ! midpoint m = (mx,my)
155 ! coordinates of the node (i,j), the lower left corner of the domain is (0 0)
156 ! ax = fdx*(i - ifds + 0.5)
157 ! ay = fdy*(j - jfds + 0.5)
160 ! the following computation of distance is also for the case
161 ! when s=e exactly or approximately
162 dam2=(ax-mx)*(ax-mx)*cx2+(ay-my)*(ay-my)*cy2 ! |a-m|^2
163 ! compute distance of a=(ax,ay) and the nearest point on the segment
164 ! [(sx,sy), (ex,ey)] as the distance of (ax,ay) from the midpoint (mx,my)
165 ! minus a correction (because of rounding errors): |a-c|^2 = |a-m|^2 - |m-c|^2
166 ! when |m-c| >= |s-e|/2 the nearest point is one of the endpoints
172 ! |m-c| = |a-m| cos (a-m,e-s)
173 ! = |a-m| (a-m).(e-s))/(|a-m|*|e-s|)
174 des2 = (ex-sx)*(ex-sx)*cx2+(ey-sy)*(ey-sy)*cy2 ! des2 = |e-s|^2
176 am_es=(ax-mx)*(ex-sx)*cx2+(ay-my)*(ey-sy)*cy2 ! am_es = (a-m).(e-s)
178 cos2 = (am_es*am_es)/dames ! cos2 = cos^2 (a-m,e-s)
179 else ! point a already is the midpoint
182 dmc2 = dam2*cos2 ! dmc2 = |m-c|^2
183 if(4.*dmc2 <= des2)then ! if |m-c|<=|e-s|/2
184 d = sqrt(max(dam2 - dmc2,0.)) ! d=|a-m|^2 - |m-c|^2, guard rounding
185 elseif(am_es>0)then ! if cos > 0, closest is e
186 d = sqrt((ax-ex)*(ax-ex)*cx2+(ay-ey)*(ay-ey)*cy2) ! |a-e|
188 d = sqrt((ax-sx)*(ax-sx)*cx2+(ay-sy)*(ay-sy)*cy2) ! |a-s|
193 ignited=ignited+1 ! count
195 if(lfn(i,j)>0 .and. lfn_new<=0) then
196 cos_ame = sqrt(cos2) ! relative distance of c from m
197 time_ign = mid_t + sign(cos_ame,am_es)*dif_th ! ignition time at c by going from m
198 tign(i,j)=time_ign ! newly ignited now
200 lfn(i,j)=min(lfn(i,j),lfn_new) ! update the level set function
203 ! write(msg,'(2i4,10f12.6)')i,j,d,sx,ex,sy,ey
205 ! write(msg,'(10f12.6)')ax,ay,dmc2,des2,cos2
207 ! write(msg,'(10f16.6)')unit_xf,unit_yf,cx2,cy2,dam2
209 ! write(msg,'(10f16.6)')des2,dames,am_es,mx,my
213 !$OMP CRITICAL(SFIRE_CORE_CRIT)
214 write(msg,'(a,2f11.6,a,2f11.6)')'ignite_fire: from',sx,sy,' to ',ex,ey
216 write(msg,'(a,2f11.2,a,f8.1,a)')'units ',unit_xf,unit_yf,' m max dist ',dmax,' m'
218 write(msg,'(a,f4.1,a,f8.1,a,i10)')' radius ',r,' time',time_ign,' ignited nodes',ignited
220 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
221 end subroutine ignite_fire
224 !**********************
227 subroutine fuel_left(&
232 lfn, tign, fuel_time, time_now, fuel_frac, fire_area)
235 !*** purpose: determine fraction of fuel remaining
236 !*** NOTE: because variables are cell centered, need halo/sync width 1 before
238 !*** Jan Mandel August 2007 email: jmandel@ucar.edu or Jan.Mandel@gmail.com
242 integer, intent(in) ::ifds,ifde,jfds,jfde,its,ite,jts,jte,ims,ime &
243 ,jms,jme,ifs,ife,jfs,jfe
244 real, intent(in), dimension(ims:ime,jms:jme)::lfn,tign,fuel_time
245 real, intent(in):: time_now
246 real, intent(out), dimension(ifs:ife,jfs:jfe)::fuel_frac
247 real, intent(out), dimension(ims:ime,jms:jme):: fire_area
249 ! ims,ime,jms,jme in memory dimensions
250 ! its,ite,jts,jte in tile dimensions (cells where fuel_frac computed)
251 ! ifs,ife,jfs,jfe in fuel_frac memory dimensions
252 ! lfn in level function, at nodes at midpoints of cells
253 ! tign in ignition time, at nodes at nodes at midpoints of cells
254 ! fuel_time in time constant of fuel, per cell
255 ! time_now in time now
256 ! fuel_frac out fraction of fuel remaining, per cell
257 ! fire_area out fraction of cell area on fire
261 integer::i,j,ir,jr,icl,jcl,isubcl,jsubcl,i2,j2,ii,jj,its1,jts1,ite1,jte1
262 real::fmax,frat,helpsum1,helpsum2,fuel_left_ff,fire_area_ff,rx,ry,tignf(2,2)
263 real,dimension(3,3)::tff,lff
264 ! help variables instead of arrays fuel_left_f and fire_area_f
265 real::lffij,lffi1j,lffij1,lffi1j1,tifij,tifi1j,tifij1,tifi1j1,tx,ty,txx,tyy
266 ! variables for calculation instead of lff(i,j) and tif(i,j)is lffij,tifij etc..#define IFCELLS (ite-its+1)*fuel_left_irl
267 #define JFCELLS (jte-jts+1)*fuel_left_jrl
268 character(len=128)::msg
269 integer::m,omp_get_thread_num
272 call check_mesh_2dim(its-1,ite+1,jts-1,jte+1,ims,ime,jms,jme)
273 call check_mesh_2dim(its,ite,jts,jte,ifs,ife,jfs,jfe)
279 if ((ir.ne.2).or.(jr.ne.2)) then
280 call crash('fuel_left: ir.ne.2 or jr.ne.2 ')
286 ! interpolate level set function to finer grid
287 #ifdef DEBUG_OUT_FUEL_LEFT
288 call write_array_m(1,IFCELLS+1,1,JFCELLS+1,1,IFCELLS+1,1,JFCELLS+1,lff,'lff',0)
289 call write_array_m(1,IFCELLS+1,1,JFCELLS+1,1,IFCELLS+1,1,JFCELLS+1,tif,'tif',0)
296 ! its-1 its ite ite+1
297 ! -------X------------|-----.-----X-----.-----|--........----|----------X----------|---------X
298 ! fine node 1 2 3 2*(ite-its+1)
299 ! fine cell 1 2 cell 2*(ite-its+1)
303 ! Loop over cells in Tile
304 ! Changes made by Volodymyr Kondratenko 09/24/2009
312 if (lfn(icl,jcl).lt.0) then
313 call crash('tign_interp: lfn on the boundary should be not burning but &
314 lfn<0, when icl=its ')
315 elseif (jcl.eq.jfs) then
316 call tign_lfn_interpolation(time_now,icl+1,jcl+1,ims,ime,jms,jme, &
319 elseif (jcl.eq.jfe) then
320 call tign_lfn_interpolation(time_now,icl+1,jcl-1,ims,ime,jms,jme, &
324 call tign_lfn_interpolation(time_now,icl+1,jcl,ims,ime,jms,jme, &
328 elseif (icl.eq.ife) then
329 if (lfn(icl,jcl).lt.0) then
330 call crash('tign_interp: lfn on the boundary should be not burning but &
333 elseif (jcl.eq.jfs) then
334 call tign_lfn_interpolation(time_now,icl-1,jcl+1,ims,ime,jms,jme, &
337 elseif (jcl.eq.jfe) then
338 call tign_lfn_interpolation(time_now,icl-1,jcl-1,ims,ime,jms,jme, &
342 call tign_lfn_interpolation(time_now,icl-1,jcl,ims,ime,jms,jme, &
346 elseif (jcl.eq.jfs) then
347 if (lfn(icl,jcl).lt.0) then
348 call crash('tign_interp: lfn on the boundary should be not burning but &
352 call tign_lfn_interpolation(time_now,icl,jcl+1,ims,ime,jms,jme, &
356 elseif (jcl.eq.jfe) then
357 if (lfn(icl,jcl).lt.0) then
358 call crash('tign_interp: lfn on the boundary should be not burning but &
362 call tign_lfn_interpolation(time_now,icl,jcl-1,ims,ime,jms,jme, &
369 ! Loop over subcells in cell #(icl,jcl)
370 write(*,*)"ifs,ife",ifs,ife
371 write(*,*)"jfs,jfe",jfs,jfe
372 write(*,*)"its,ite,jts,jte",its,ite,jts,jte
373 write(*,*)"ims,ime,jms,jme",ims,ime,jms,jme
376 call tign_lfn_interpolation(time_now,icl,jcl,ims,ime,jms,jme, &
382 !we use 4 points of each subcell in fuel_left_cell_1
383 ! and in fuel_left_cell_2: bottome left are (1,1), (1,2), (2,1), (2,2)
384 if(fuel_left_method.eq.1)then
385 call fuel_left_cell_1( fuel_left_ff, fire_area_ff, &
386 lff(isubcl,jsubcl),lff(isubcl,jsubcl+1),lff(isubcl+1,jsubcl),lff(isubcl+1,jsubcl+1), &
387 tff(isubcl,jsubcl),tff(isubcl,jsubcl+1),tff(isubcl+1,jsubcl),tff(isubcl+1,jsubcl+1), &
388 time_now, fuel_time(icl,jcl))
389 elseif(fuel_left_method.eq.2)then
390 fire_area_ff=0 ! initialize to something until computed in fuel_left_f(i,j)
391 fuel_left_ff=fuel_left_cell_3( &
392 lff(isubcl,jsubcl),lff(isubcl,jsubcl+1),lff(isubcl+1,jsubcl),lff(isubcl+1,jsubcl+1), &
393 tff(isubcl,jsubcl),tff(isubcl,jsubcl+1),tff(isubcl+1,jsubcl),tff(isubcl+1,jsubcl+1), &
394 time_now, fuel_time(icl,jcl))
395 ! dont forget to change fire_area_ff here
397 call crash('fuel_left: unknown fuel_left_method')
401 if(fire_area_ff.lt.-1e-6 .or. &
402 (fire_area_ff.eq.0. .and. fuel_left_ff.lt.1.-1e-6))then
403 !$OMP CRITICAL(SFIRE_CORE_CRIT)
404 write(msg,'(a,2i6,2(a,f11.8))')'fuel_left: at node',i,j, &
405 ' of refined mesh fuel burnt',1-fuel_left_ff,' fire area',fire_area_ff
406 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
410 helpsum1=helpsum1+fuel_left_ff
411 helpsum2=helpsum2+fire_area_ff
414 fuel_frac(icl,jcl)=helpsum1
415 fire_area(icl,jcl)=helpsum2
422 #ifdef DEBUG_OUT_FUEL_LEFT
423 call write_array_m(its,ite,jts,jte,ims,ime,jms,jme,fire_area,'fire_area',0)
424 call write_array_m(its,ite,jts,jte,ims,ime,jms,jme,fuel_frac,'fuel_frac',0)
425 call write_array_m(1,IFCELLS,1,JFCELLS,1,IFCELLS,1,JFCELLS,fuel_left_f,'fuel_left_f',0)
426 call write_array_m(1,IFCELLS,1,JFCELLS,1,IFCELLS,1,JFCELLS,fire_area_f,'fire_area_f',0)
429 ! finish the averaging
432 fuel_frac(i,j) = fuel_frac(i,j) /(ir*jr) ! multiply by weight for averaging over coarse cell
433 fire_area(i,j) = fire_area(i,j) /(ir*jr) !
437 ! consistency check after sum
441 if(fire_area(i,j).eq.0.)then
442 if(fuel_frac(i,j).lt.1.-1e-6)then
443 !$OMP CRITICAL(SFIRE_CORE_CRIT)
444 write(msg,'(a,2i6,2(a,f11.8))')'fuel_left: at node',i,j, &
445 ' fuel burnt',1-fuel_frac(i,j),' but fire area',fire_area(i,j)
446 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
450 frat=(1-fuel_frac(i,j))/fire_area(i,j)
455 !$OMP CRITICAL(SFIRE_CORE_CRIT)
456 write(msg,'(a,4i6,a,f10.7)')'fuel_left: tile',its,ite,jts,jte,' max fuel burnt/area',fmax
457 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
462 end subroutine fuel_left
465 !************************
470 !*************************
471 !Subroutine that is calculating tign and lfn of four endpoints of the subcell
472 ! that is located at isubcl,jsubcl of the cell -(icl,jcl)
474 subroutine tign_lfn_interpolation(time_now,icl,jcl,ims,ime,jms,jme, &
476 real, intent(in):: time_now ! not ignited nodes will have tign set to >= time_now
477 integer, intent(in) :: icl,jcl
478 integer, intent(in) :: ims,ime,jms,jme ! memory dimensions of all arrays
479 real, intent(in), dimension(ims:ime,jms:jme)::lfn,tign
480 real, intent(out),dimension(3,3)::tff,lff
482 ! (3,1)-------------(3,2)-------------(3,3)
489 ! (2,1)--------node-(icl,jcl)---------(2,3)-----------(icl,jcl+1)-------------|
492 ! | (1,1) | (1,2) | each fire mesh cell is decomposed in 4
493 ! | | | tff,lff is computed at the nodes of
494 ! | | | the subcells, numbered (1,1)...(3,3)
495 ! (1,1)-------------(1,2)-------------(1,3)--
501 ! -------------(icl-1,jcl------------------
505 !**********************
507 ! Direct calculation tif and lff, avoiding arrays, just for case ir=jr=2
508 ! Checking whether icl or jcl is on the boundary
511 lff(1,1)=0.25*(lfn(icl-1,jcl-1)+lfn(icl-1,jcl)+lfn(icl,jcl-1)+lfn(icl,jcl))
512 call tign_four_pnts_interp(tign(icl-1,jcl-1),tign(icl-1,jcl),tign(icl,jcl-1), &
513 tign(icl,jcl),lfn(icl-1,jcl-1),lfn(icl-1,jcl), &
514 lfn(icl,jcl-1),lfn(icl,jcl),lff(1,1),tff(1,1),time_now)
516 lff(1,2)=0.5*(lfn(icl-1,jcl)+lfn(icl,jcl))
517 call tign_line_interp(tign(icl-1,jcl),tign(icl,jcl),lfn(icl-1,jcl),lfn(icl,jcl), &
518 lff(1,2),tff(1,2),time_now)
521 lff(1,3)=0.25*(lfn(icl-1,jcl+1)+lfn(icl-1,jcl)+lfn(icl,jcl+1)+lfn(icl,jcl))
522 call tign_four_pnts_interp(tign(icl-1,jcl),tign(icl-1,jcl+1),tign(icl,jcl), &
523 tign(icl,jcl+1),lfn(icl-1,jcl),lfn(icl-1,jcl+1), &
524 lfn(icl,jcl),lfn(icl,jcl+1),lff(1,3),tff(1,3),time_now)
526 lff(2,1)=0.5*(lfn(icl,jcl-1)+lfn(icl,jcl))
527 call tign_line_interp(tign(icl,jcl-1),tign(icl,jcl),lfn(icl,jcl-1),lfn(icl,jcl), &
528 lff(2,1),tff(2,1),time_now)
529 lff(2,2)=lfn(icl,jcl)
530 tff(2,2)=tign(icl,jcl)
532 lff(2,3)=0.5*(lfn(icl,jcl+1)+lfn(icl,jcl))
533 call tign_line_interp(tign(icl,jcl+1),tign(icl,jcl),lfn(icl,jcl+1),lfn(icl,jcl), &
534 lff(2,3),tff(2,3),time_now)
536 lff(3,1)=0.25*(lfn(icl,jcl-1)+lfn(icl,jcl)+lfn(icl+1,jcl-1)+lfn(icl+1,jcl))
537 call tign_four_pnts_interp(tign(icl,jcl-1),tign(icl,jcl),tign(icl+1,jcl-1), &
538 tign(icl+1,jcl),lfn(icl,jcl-1),lfn(icl,jcl), &
539 lfn(icl+1,jcl-1),lfn(icl+1,jcl),lff(3,1),tff(3,1),time_now)
541 lff(3,2)=0.5*(lfn(icl+1,jcl)+lfn(icl,jcl))
542 call tign_line_interp(tign(icl+1,jcl),tign(icl,jcl),lfn(icl+1,jcl),lfn(icl,jcl), &
543 lff(3,2),tff(3,2),time_now)
545 lff(3,3)=0.25*(lfn(icl,jcl)+lfn(icl,jcl+1)+lfn(icl+1,jcl)+lfn(icl+1,jcl+1))
546 call tign_four_pnts_interp(tign(icl,jcl),tign(icl,jcl+1),tign(icl+1,jcl), &
547 tign(icl+1,jcl+1),lfn(icl,jcl),lfn(icl,jcl+1), &
548 lfn(icl+1,jcl),lfn(icl+1,jcl+1),lff(3,3),tff(3,3),time_now)
552 end subroutine tign_lfn_interpolation
556 subroutine tign_line_interp(tign1,tign2,lfn1,lfn2,lfn_subcl,tign_subcl,time_now)
558 real,intent(in) :: tign1,tign2,lfn1,lfn2,lfn_subcl,time_now
559 real,intent(out) :: tign_subcl
563 ! write(*,*)"lfn1,lfn2,tign1,tign2,lfn_subcl,timenow",lfn1,lfn2,tign1,tign2,lfn_subcl,time_now
565 if((lfn1.le.0).AND.(lfn2.le.0)) then
566 tign_subcl=0.5*(tign1+tign2)
567 elseif((lfn1.gt.0).AND.(lfn2.gt.0))then
568 if ((abs(tign1-time_now).gt.err).or.(abs(tign2-time_now).gt.err)) then
569 write(*,*)"lfn1,lfn2,tign1,tign2,timenow",lfn1,lfn2,tign1,tign2,time_now
570 call crash('line_interp: when lfn1(2)>0 their tign1(2) should = time_now')
574 elseif(lfn_subcl.gt.0) then
575 if ((abs(tign1-time_now).gt.err).OR.(abs(tign2-time_now).gt.err)) then
576 call crash('tign_line_interp one of tign1,2 should be equal time_now')
582 !case when E is on fire
583 ! tign_subcl~=c*lfn_subcl+time_now;
587 elseif (lfn2.le.0) then
591 call crash('tign_line_interp: if E is on fire then one of lfn1 or lfn2 &
592 should be negative');
595 call crash('tign_ should be less than time_now');
598 tign_subcl=c*lfn_subcl+time_now;
601 end subroutine tign_line_interp
603 !************************
606 subroutine tign_four_pnts_interp(tign1,tign2,tign3,tign4,lfn1,lfn2, &
607 lfn3,lfn4,lfn_subcl,tign_subcl,time_now)
609 real,intent(in) :: tign1,tign2,tign3,tign4
610 real,intent(in) :: lfn1,lfn2,lfn3,lfn4,lfn_subcl,time_now
611 real,intent(out) :: tign_subcl
616 if((lfn1.le.0).AND.(lfn2.le.0).AND.(lfn3.le.0).AND.(lfn4.le.0)) then
617 tign_subcl=0.25*(tign1+tign2+tign3+tign4)
618 elseif((lfn1.gt.0).AND.(lfn2.gt.0).AND.(lfn3.gt.0).AND.(lfn4.gt.0))then
619 !if ((abs(tign1-time_now).gt.err).OR.(abs(tign2-time_now).gt.err).OR.(abs(tign3-time_now).gt.err).OR.(abs(tign4-time_now).gt.err)) then
620 ! call crash('tign_four_pnts_interp: when lfn1(2,3,4)>0 their tign1(2,3,4) should = time_now')
624 elseif(lfn_subcl.gt.0) then
625 ! if ((abs(tign1-time_now).gt.err).OR.(abs(tign2-time_now).gt.err).OR.(abs(tign3-time_now).gt.err).OR.(abs(tign4-time_now).gt.err)) then
626 ! call crash('tign_line_interp one of tign1(2,3,4) should be equal time_now')
632 !case when E is on fire
633 ! tign_subcl~=c*lfn_subcl+time_now;
637 ! if (tign1.gt.time_now)
638 ! call crash('tign_four_pnts_interp tign1 should be less then time_now');
640 ! Can not assign to a named constant
642 b=b+(tign1-time_now)*lfn1;
646 ! if (tign2.gt.time_now)
647 ! call crash('tign_four_pnts_interp tign2 should be less then time_now');
649 ! Can not assign to a named constant
651 b=b+(tign2-time_now)*lfn2;
655 ! if (tign3.gt.time_now)
657 ! call crash('tign_four_pnts_interp tign3 should be less then time_now');
659 ! Can not assign to a named constant
661 b=b+(tign3-time_now)*lfn3;
665 ! if (tign4.gt.time_now)
666 ! call crash('tign_four_pnts_interp tign4 should be less then time_now');
668 ! Can not assign to a named constant
670 b=b+(tign4-time_now)*lfn4;
673 if ((abs(a).lt.err).or.(b.lt.0)) then
674 call crash('tign_four_pnts_interp: if E is on fire then one of cells &
675 should be on fire or tign_ should be less than time_now')
678 tign_subcl=c*lfn_subcl+time_now;
682 end subroutine tign_four_pnts_interp
686 !************************
690 subroutine fuel_left_cell_1( fuel_frac_left, fire_frac_area, &
691 lfn00,lfn01,lfn10,lfn11, &
692 tign00,tign01,tign10,tign11,&
693 time_now, fuel_time_cell)
694 !*** purpose: compute the fuel fraction left in one cell
697 real, intent(out):: fuel_frac_left, fire_frac_area !
698 real, intent(in)::lfn00,lfn01,lfn10,lfn11 ! level set function at 4 corners of the cell
699 real, intent(in)::tign00,tign01,tign10,tign11! ignition time at the 4 corners of the cell
700 real, intent(in)::time_now ! the time now
701 real, intent(in)::fuel_time_cell ! time to burns off to 1/e
703 ! The area burning is given by the condition L <= 0, where the function P is
704 ! interpolated from lfn(i,j)
706 ! The time since ignition is the function T, interpolated in from tign(i,j)-time_now.
707 ! The values of tign(i,j) where lfn(i,j)>=0 are ignored, tign(i,j)=0 is taken
710 ! The function computes an approxmation of the integral
715 ! fuel_frac_left = 1 - | 1 - exp(-T(x,y)/fuel_time_cell)) dxdy
722 ! When the cell is not burning at all (all lfn>=0), then fuel_frac(i,j)=1.
723 ! Because of symmetries, the result should not depend on the mesh spacing dx dy
724 ! so dx=1 and dy=1 assumed.
729 ! (0,1) -----O--(1,1) O = points on the fireline, T=tnow
730 ! | \ | A = the burning area for computing
731 ! | \| fuel_frac(i,j)
735 ! (0,0)---------(1,0)
738 ! Approximations allowed:
739 ! The fireline can be approximated by straight line(s).
740 ! When all cell is burning, approximation by 1 point Gaussian quadrature is OK.
743 ! 1. The output should be a continuous function of the arrays lfn and
744 ! tign whenever lfn(i,j)=0 implies tign(i,j)=tnow.
745 ! 2. The output should be invariant to the symmetries of the input in each cell.
746 ! 3. Arbitrary combinations of the signs of lfn(i,j) should work.
747 ! 4. The result should be at least 1st order accurate in the sense that it is
748 ! exact if the time from ignition is a linear function.
750 ! If time from ignition is approximated by polynomial in the burnt
751 ! region of the cell, this is integral of polynomial times exponential
752 ! over a polygon, which can be computed exactly.
754 ! Requirement 4 is particularly important when there is a significant decrease
755 ! of the fuel fraction behind the fireline on the mesh scale, because the
756 ! rate of fuel decrease right behind the fireline is much larger
757 ! (exponential...). This will happen when
759 ! change of time from ignition within one mesh cell / fuel_time_cell is not << 1
761 ! This is the same as
764 ! X = ------------------------- is not << 1
765 ! fireline speed * fuel_time_cell
768 ! When X is large then the fuel burnt in one timestep in the cell is
769 ! approximately proportional to length of fireline in that cell.
771 ! When X is small then the fuel burnt in one timestep in the cell is
772 ! approximately proportional to the area of the burning region.
779 real::ps,aps,area,ta,out
780 real::t00,t01,t10,t11
781 real,parameter::safe=tiny(aps)
782 character(len=128)::msg
784 ! the following algorithm is a very crude approximation
786 ! minus time since ignition, 0 if no ignition yet
787 ! it is possible to have 0 in fire region when ignitin time falls in
788 ! inside the time step because lfn is updated at the beginning of the time step
791 if(lfn00>0. .or. t00>0.)t00=0.
793 if(lfn01>0. .or. t01>0.)t01=0.
795 if(lfn10>0. .or. t10>0.)t10=0.
797 if(lfn11>0. .or. t11>0.)t11=0.
799 ! approximate burning area, between 0 and 1
800 ps = lfn00+lfn01+lfn10+lfn11
801 aps = abs(lfn00)+abs(lfn01)+abs(lfn10)+abs(lfn11)
803 area =(-ps/aps+1.)/2.
804 area = max(area,0.) ! make sure area is between 0 and 1
807 ! average negative time since ignition
808 ta=0.25*(t00+t01+t10+t11)
810 ! exp decay in the burning area
812 !if(area>0.)out=1. - area*(1. - exp(ta/fuel_time_cell))
813 if(area>0)out=area*exp(ta/fuel_time_cell) + (1. - area)
816 !$OMP CRITICAL(SFIRE_CORE_CRIT)
817 write(msg,*)'out=',out,'>1 area=',area,' ta=',ta
819 write(msg,*)'tign=',tign00,tign01,tign10,tign11,' time_now=',time_now
820 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
822 !call message('WARNING: fuel_left_cell_1: fuel fraction > 1')
823 call crash('fuel_left_cell_1: fuel fraction > 1')
826 !out = max(out,0.) ! make sure out is between 0 and 1
830 fire_frac_area = area
832 end subroutine fuel_left_cell_1
835 !****************************************
837 ! function calculation fuel_frac made by Volodymyr Kondratenko on the base of
838 ! the code made by Jan Mandel and Minjeong
840 real function fuel_left_cell_3( &
841 lfn00,lfn01,lfn10,lfn11, &
842 tign00,tign01,tign10,tign11,&
843 time_now, fuel_time_cell)
844 !*** purpose: compute the fuel fraction left in one cell
847 real, intent(in)::lfn00,lfn01,lfn10,lfn11 ! level set function at 4 corners of the cell
848 real, intent(in)::tign00,tign01,tign10,tign11! ignition time at the 4 corners of the cell
849 real, intent(in)::time_now ! the time now
850 real, intent(in)::fuel_time_cell ! time to burns off to 1/e
852 ! The area burning is given by the condition L <= 0, where the function P is
853 ! interpolated from lfn(i,j)
855 ! The time since ignition is the function T, interpolated in from tign(i,j)-time_now.
856 ! The values of tign(i,j) where lfn(i,j)>=0 are ignored, tign(i,j)=0 is taken
859 ! The function computes an approxmation of the integral
864 ! fuel_frac_left = 1 - | 1 - exp(-T(x,y)/fuel_time_cell)) dxdy
871 ! When the cell is not burning at all (all lfn>=0), then fuel_frac(i,j)=1.
872 ! Because of symmetries, the result should not depend on the mesh spacing dx dy
873 ! so dx=1 and dy=1 assumed.
878 ! (0,1) -----O--(1,1) O = points on the fireline, T=tnow
879 ! | \ | A = the burning area for computing
880 ! | \| fuel_frac(i,j)
884 ! (0,0)---------(1,0)
887 ! Approximations allowed:
888 ! The fireline can be approximated by straight line(s).
889 ! When all cell is burning, approximation by 1 point Gaussian quadrature is OK.
892 ! 1. The output should be a continuous function of the arrays lfn and
893 ! tign whenever lfn(i,j)=0 implies tign(i,j)=tnow.
894 ! 2. The output should be invariant to the symmetries of the input in each cell.
895 ! 3. Arbitrary combinations of the signs of lfn(i,j) should work.
896 ! 4. The result should be at least 1st order accurate in the sense that it is
897 ! exact if the time from ignition is a linear function.
899 ! If time from ignition is approximated by polynomial in the burnt
900 ! region of the cell, this is integral of polynomial times exponential
901 ! over a polygon, which can be computed exactly.
903 ! Requirement 4 is particularly important when there is a significant decrease
904 ! of the fuel fraction behind the fireline on the mesh scale, because the
905 ! rate of fuel decrease right behind the fireline is much larger
906 ! (exponential...). This will happen when
908 ! change of time from ignition within one mesh cell * fuel speed is not << 1
910 ! This is the same as
912 ! mesh cell size*fuel_speed
913 ! ------------------------- is not << 1
917 ! When X is large then the fuel burnt in one timestep in the cell is
918 ! approximately proportional to length of fireline in that cell.
920 ! When X is small then the fuel burnt in one timestep in the cell is
921 ! approximately proportional to the area of the burning region.
924 !call crash('fuel_left_cell_3: not implemented, please use fire_fuel_left_method=1')
925 !fuel_left_cell_3=0. ! to avoid compiler warning about value not set
926 !end function fuel_left_cell_3
933 real::ps,aps,area,ta,out
934 real::t00,t01,t10,t11
935 real,parameter::safe=tiny(aps)
936 character(len=128)::msg
937 real::dx,dy ! mesh sizes
942 integer::mmax,nb,nmax,pmax,nin,nout
943 parameter(mmax=3,nb=64,nmax=8,pmax=8)
944 integer lda, ldb, lwork, info
945 parameter (lda=nmax, ldb=nmax, lwork=mmax+nmax+nb*(nmax+pmax))
947 real,dimension(lda,mmax):: mA
948 real,dimension(nmax):: vecD
949 real,dimension(lwork):: WORK
950 real,dimension(pmax):: vecY
951 real,dimension(mmax):: vecX
952 real,dimension(ldb,pmax)::mB
953 real,dimension(mmax)::u
958 real,dimension(8,2)::xylist,xytlist
959 real,dimension(8)::tlist,llist,xt
960 real,dimension(5)::xx,yy
961 real,dimension(5)::lfn,tign
964 real::tt,x0,y0,xts,xte,yts,yte,xt1,xt2
965 real::lfn0,lfn1,dist,nr,s,errQ,ae,ce,ceae,a0,a1,a2,d,cet
967 real::upper,lower,ah,ch,aa,cc,aupp,cupp,alow,clow
968 real,dimension(2,2)::mQ
969 real,dimension(2)::ut
974 real, parameter:: zero=0.,one=1.,eps=epsilon(zero)
976 !!!! For finite differences by VK
977 real::tign_middle,dt_dx,dt_dy,lfn_middle,a,b,c
981 double precision :: dnrm2
984 ! external subroutines
987 !executable statements
989 ! a very crude approximation - replace by a better code
990 ! call check_mesh_2dim(ids,ide+1,jds,jde+1,ims,ime,jms,jme)
994 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
995 !comment - changed tign-time_now to time_now-tign
996 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
998 if(lfn00>=0. .or. t00<0.)t00=0.
1000 if(lfn01>=0. .or. t01<0.)t01=0.
1002 if(lfn10>=0. .or. t10<0.)t10=0.
1004 if(lfn11>=0. .or. t11<0.)t11=0.
1006 !*** case0 Do nothing
1007 if ( lfn00>=0 .and. lfn10>=0 .and. lfn01>=0 .and. lfn11>=0 ) then
1009 out = 1.0 ! fuel_left, no burning
1010 !*** case4 all four coners are burning
1011 else if (lfn00<=0 .and. lfn10<=0 .and. lfn01<=0 .and. lfn11<=0) then
1016 ! T=u(1)*x+u(2)*y+u(3)
1018 ! t(0,fd(2))=tign(1,2)
1019 ! t(fd(1),0)=tign(2,1)
1020 ! t(fd(1),fd(2))=tign(2,2)
1021 ! t(g/2,h/2)=sum(tign(i,i))/4
1022 ! dt/dx=(1/2h)*(t10-t00+t11-t01)
1023 ! dt/dy=(1/2h)*(t01-t00+t11-t10)
1024 ! approximate T(x,y)=u(1)*x+u(2)*y+u(3) Using finite differences
1025 ! t(x,y)=t(h/2,h/2)+(x-h/2)*dt/dx+(y-h/2)*dt/dy
1028 ! u(3)=t(h/2,h/2)-h/2(dt/dx+dt/dy)
1030 tign_middle=(t00+t01+t10+t11)/4
1031 write(*,*)"tign_middle",tign_middle
1032 write(*,*)"t00,t01,t10,t11",t00,t01,t10,t11
1034 ! since mesh_size is 1 we replace fd(1) and fd(2) by 1
1035 dt_dx=(t10-t00+t11-t01)/2
1036 dt_dy=(t01-t00+t11-t10)/2
1038 write(*,*)"dt_dx,dt_dy",dt_dx,dt_dy
1039 write(*,*)"dx,dy",dx,dy
1043 u(3)=tign_middle-(dt_dx+dt_dy)/2
1045 Write(*,*)"u=",u(1),u(2),u(3)
1048 u(1)=-u(1)/fuel_time_cell
1049 u(2)=-u(2)/fuel_time_cell
1050 u(3)=-u(3)/fuel_time_cell
1051 write(*,*)"u/fuel_time_cell",u(1),u(2),u(3)
1053 write(*,*)"intexp(u(1)),intexp(u(2))",intexp(u(1)*dx),intexp(u(2)*dy)
1055 !fuel_burn(i,j)=1-exp(u(3))*intexp(u(1)*dx)*intexp(u(2)*dy)
1058 out=1-exp(u(3))*intexp(s1)*intexp(s2)
1059 print *,intexp(s1),intexp(s2),out
1060 if ( out<0 .or. out>1.0 ) then
1061 print *,'case4, out should be between 0 and 1'
1067 ! set xx, yy for the coner points
1068 ! move these values out of i and j loop to speed up
1089 npoint = 0 ! number of points in polygon
1094 if ( lfn0 <= 0.0 ) then
1096 xylist(npoint,1)=xx(k)
1097 xylist(npoint,2)=yy(k)
1098 tlist(npoint)=-tign(k)
1101 if ( lfn0*lfn1 < 0 ) then
1104 x0=xx(k)+( xx(k+1)-xx(k) )*tt
1105 y0=yy(k)+( yy(k+1)-yy(k) )*tt
1108 tlist(npoint)=0 ! on fireline
1113 ! make the list circular
1114 tlist(npoint+1)=tlist(1)
1115 llist(npoint+1)=llist(1)
1116 xylist(npoint+1,1)=xylist(1,1)
1117 xylist(npoint+1,2)=xylist(1,2)
1121 !print *,'after LS in case3'pproximation of the plane for lfn using finite
1123 ! approximate L(x,y)=u(1)*x+u(2)*y+u(3)
1124 lfn_middle=(lfn00+lfn01+lfn10+lfn11)/4
1125 print *,"lfn_middle",lfn_middle
1126 dt_dx=(lfn10-lfn00+lfn11-lfn01)/2
1127 dt_dy=(lfn01-lfn00+lfn11-lfn10)/2
1128 print *,"dt_dx,dt_dy",dt_dx,dt_dy
1131 u(3)=lfn_middle-(dt_dx+dt_dy)/2
1132 ! finding the coefficient c, reminder we work over one subcell only
1133 ! T(x,y)=c*L(x,y)+time_now
1134 write(*,*)"vector u before c",u(1),u(2),u(3)
1138 if (lfn00 <= 0) then
1141 call crash('fuel_burnt_fd: tign(i1) should be less then time_now')
1145 write(*,*)"t00,time_now,lfn00",t00,time_now,lfn00
1150 if (lfn01 <= 0) then
1153 call crash('fuel_burnt_fd: tign(i1) should be less then time_now')
1164 call crash('fuel_burnt_fd: tign(i1) should be less then time_now')
1174 call crash('fuel_burnt_fd: tign(i1) should be less then time_now')
1184 call crash('fuel_burnt_fd: if c is on fire then one of cells should be on fire')
1191 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1193 !print *,'vecX from LS',vecX
1194 !print *,'tign inputed',tign00,tign10,tign11,tign01
1196 write(*,*)"vector u",u(1),u(2),u(3)
1197 ! rotate to gradient on x only
1198 nr = sqrt(u(1)**2+u(2)**2)
1199 if(.not.nr.gt.eps)then
1209 ! mat vec multiplication
1210 call matvec(mQ,2,2,u,3,ut,2,2,2)
1211 errQ = ut(2) ! should be zero
1212 ae = -ut(1)/fuel_time_cell
1213 ce = -u(3)/fuel_time_cell
1215 call matmatp(xylist,8,2,mQ,2,2,xytlist,8,2,npoint+1,2,2)
1216 call sortxt( xytlist, 8,2, xt,8,npoint )
1229 if ( xt2-xt1 > eps*100 ) then
1237 if ( (xts>xt1 .and. xte>xt1) .or. &
1238 (xts<xt2 .and. xte<xt2) ) then
1242 aa = (yts-yte)/(xts-xte)
1243 cc = (xts*yte-xte*yts)/(xts-xte)
1255 end if!(xts>xt1 .and. xte>xt1)
1257 ce=cet !use stored ce
1258 if (ae*xt1+ce > 0 ) then
1259 ce=ce-(ae*xt1+ce)!shift small amounts exp(-**)
1261 if (ae*xt2+ce > 0) then
1267 ! integrate (ah*x+ch)*(1-exp(ae*x+ce) from xt1 to xt2
1268 ! numerically sound for ae->0, ae -> infty
1269 ! this can be important for different model scales
1270 ! esp. if someone runs the model in single precision!!
1271 ! s1=int((ah*x+ch),x,xt1,xt2)
1272 s1 = (xt2-xt1)*((1./2.)*ah*(xt2+xt1)+ch)
1273 ! s2=int((ch)*(-exp(ae*x+ce)),x,xt1,xt2)
1275 s2 = -ch*exp(ae*(xt1+ceae))*(xt2-xt1)*intexp(ae*(xt2-xt1))
1276 ! s3=int((ah*x)*(-exp(ae*x+ce)),x,xt1,xt2)
1277 ! s3=int((ah*x)*(-exp(ae*(x+ceae))),x,xt1,xt2)
1278 ! expand in Taylor series around ae=0
1279 ! collect(expand(taylor(int(x*(-exp(ae*(x+ceae))),x,xt1,xt2)*ae^2,ae,4)/ae^2),ae)
1280 ! =(1/8*xt1^4+1/3*xt1^3*ceae+1/4*xt1^2*ceae^2-1/8*xt2^4-1/3*xt2^3*ceae-1/4*xt2^2*ceae^2)*ae^2
1281 ! + (-1/3*xt2^3-1/2*xt2^2*ceae+1/3*xt1^3+1/2*xt1^2*ceae)*ae
1282 ! + 1/2*xt1^2-1/2*xt2^2
1284 ! coefficient at ae^2 in the expansion, after some algebra
1285 a2=(xt1-xt2)*((1./4.)*(xt1+xt2)*ceae**2+(1./3.)* &
1286 (xt1**2+xt1*xt2+xt2**2)*ceae+(1./8.)* &
1287 (xt1**3+xt1*(xt2**2)+xt1**2*xt2+xt2**3))
1290 if (abs(d)>eps) then
1291 ! since ae*xt1+ce<=0 ae*xt2+ce<=0 all fine for large ae
1292 ! for ae, ce -> 0 rounding error approx eps/ae^2
1293 s3=( exp(ae*(xt1+ceae))*(ae*xt1-1)-&
1294 exp(ae*(xt2+ceae))*(ae*xt2-1) )/(ae**2)
1296 !we do not worry about rounding as xt1 -> xt2, then s3 -> 0
1298 ! coefficient at ae^1 in the expansion
1299 a1=(xt1-xt2)*((1./2.)*ceae*(xt1+xt2)+(1./3.)*&
1300 (xt1**2+xt1*xt2+xt2**2))
1301 ! coefficient at ae^0 in the expansion for ae->0
1302 a0=(1./2.)*(xt1-xt2)*(xt1+xt2)
1303 s3=a0+a1*ae+a2*ae**2; ! approximate the integral
1308 if(out<0 .or. out>1) then
1309 print *,':fuel_fraction should be between 0 and 1'
1310 !print *, 'eps= ', eps
1311 !print *, 'xt1= ', xt1, 'xt2= ', xt2
1312 !print *,'ae= ',ae,'ce= ',ce,'ah= ',ah,'ch= ',ch
1313 !print *,'a0= ', a0,'a1= ', a1,'a2= ', a2
1314 print *,'s1= ', s1,'s2= ', s2,'s3= ', s3
1315 print *,':fuel_fraction =',out
1321 out=1-out !fuel_left
1322 end if ! if case0, elseif case4 ,else case123
1324 write(*,*)"out=",out
1327 if(out>1. .or. out<0.)call crash('fuel_left_cell_3: fuel fraction out of bounds [0,1]')
1328 fuel_left_cell_3 = out
1329 end function fuel_left_cell_3
1336 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1337 real function intexp(ab)
1343 real, parameter:: zero=0.,one=1.,eps=epsilon(zero)
1345 !eps = 2.2204*(10.0**(-8))!from matlab
1346 if ( eps < abs(ab)**3/6. ) then
1347 intexp=(exp(ab)-1)/ab
1353 !****************************************
1355 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1357 !****************************************
1362 !****************************************
1368 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1369 subroutine sortxt(xytlist,nrow,ncolumn,xt,nxt,nvec)
1371 integer::nrow,ncolumn,nxt,nvec
1372 real,dimension(nrow,ncolumn)::xytlist
1373 real,dimension(nxt)::xt
1384 if ( xt(i) > xt(j) ) then
1392 end subroutine !sortxt
1394 !****************************************
1396 subroutine matvec(A,m,n,V,nv,out,nout,nrow,ncolumn)
1398 integer::m,n,nv,nout,nrow,ncolumn
1399 real,dimension(m,n)::A ! allocated m by n
1400 real,dimension(nv)::V ! allocated nv
1401 real,dimension(nout)::out! allocated nout
1408 out(i)=out(i)+A(i,j)*V(j)
1413 !****************************************
1415 subroutine matmatp(A,mA,nA,B,mB,nB,C,mC,nC,nrow,ncolumn,nP)
1417 integer::mA,nA,mB,nB,mC,nC,nrow,ncolumn,nP
1418 real,dimension(mA,nA)::A ! allocated m by n
1419 real,dimension(mB,nB)::B ! allocated m by n
1420 real,dimension(mC,nC)::C ! allocated m by n
1426 C(i,j)=C(i,j)+A(i,k)*B(j,k) ! B'
1433 !****************************************
1437 subroutine prop_ls( id, & ! for debug
1438 ids,ide,jds,jde, & ! domain dims
1439 ims,ime,jms,jme, & ! memory dims
1440 ips,ipe,jps,jpe, & ! patch - nodes owned by this process
1441 its,ite,jts,jte, & ! tile dims
1442 ts,dt,dx,dy, & ! scalars in
1443 tbound, & ! scalars out
1444 lfn_in,lfn_out,tign,ros, & ! arrays inout
1449 !*** purpose: advance level function in time
1451 ! Jan Mandel August 2007 - February 2008
1455 ! Propagation of closed curve by a level function method. The level function
1456 ! lfn is defined by its values at the nodes of a rectangular grid.
1457 ! The area where lfn < 0 is inside the curve. The curve is
1458 ! described implicitly by lfn=0. Points where the curve intersects gridlines
1459 ! can be found by linear interpolation from nodes.
1461 ! The level function is advanced from time ts to time ts + dt.
1463 ! The level function should be initialized to (an approximation of) the signed
1464 ! distance from the curve. If the initial curve is a circle, the initial level
1465 ! function is simply the distance from the center minus the radius.
1467 ! The curve moves outside with speed given by function speed_func.
1469 ! Method: Godunov/ENO method for the normal motion. The timestep is checked for
1470 ! CFL condition. For a straight segment in a constant field and locally linear
1471 ! level function, the method reduces to the exact normal motion. The advantage of
1472 ! the level set method is that it treats automatically special cases such as
1473 ! the curve approaching itself and merging components of the area inside the curve.
1475 ! Based on S. Osher and R. Fedkiw, Level set methods and dynamic implicit surfaces,
1476 ! Springer, 2003, Sec. 6.4, as implemented in toolboxLS for Matlab by
1477 ! I. Mitchell, A toolbox of Level Set Methods (Version 1.1), TR-2007-11,
1478 ! Dept. Computer Science, University of British Columbia, 2007
1479 ! http://www.cs.ubc.ca/\~mitchell/Toolbo\LS
1484 ! id in unique identification for prints and dumps
1485 ! ids,ide,jds,jde in domain dimensions
1486 ! ims,ime,jms,jme in memory dimensions
1487 ! its,ite,jts,jte in tile dimensions
1490 ! dx,dy in grid spacing
1491 ! tbound out bound on stable time step from CFL condition, if tbound>=dt then OK
1492 ! lfn_in,lfn_out inout,out the level set function at nodes
1493 ! tign inout the ignition time at nodes
1495 ! The dimensions are cell-based, the nodal value is associated with the south-west corner.
1496 ! The whole computation is on domain indices ids:ide+1,jds:jde+1.
1498 ! The region where new lfn and tign are computed is the tile its:ite,jts:jte
1499 ! except when the tile is at domain upper boundary, an extra band of points is added:
1500 ! if ite=ide then region goes up to ite+1, if jte=jde then region goes up to jte+1.
1502 ! The time step requires values from 2 rows of nodes beyond the region except when at the
1503 ! domain boundary one-sided derivatives are used. This is implemented by extending the input
1504 ! beyond the domain boundary so sufficient memory bounds must be allocated.
1505 ! The update on all tiles can be done in parallel. To avoid the race condition (different regions
1506 ! of the same array updated by different threads), the in and out versions of the
1507 ! arrays lft and tign are distinct. If the time step dt is larger
1508 ! that the returned tbound, the routine should be called again with timestep td<=tbound, and then
1509 ! having distinct inputs and outputs comes handy.
1516 integer,intent(in)::id,ims,ime,jms,jme,ids,ide,jds,jde,its,ite,jts,jte,ips,ipe,jps,jpe
1517 real,dimension(ims:ime,jms:jme),intent(inout)::lfn_in,tign
1518 real,dimension(ims:ime,jms:jme),intent(out)::lfn_out,ros
1519 real,intent(in)::dx,dy,ts,dt
1520 real,intent(out)::tbound
1521 type(fire_params),intent(in)::fp
1529 real,dimension(IMTS:IMTE,JMTS:JMTE):: tend, lfn1 ! region-sized with halo
1531 real::grad2,rr,tbound2,a,a1 ! a=0 euler, a=0.5 heun
1533 real::gradx,grady,aspeed,err,aerr,time_now
1534 integer::ihs,ihe,jhs,jhe
1535 integer::ihs2,ihe2,jhs2,jhe2
1536 integer::i,j,its1,ite1,jts1,jte1,k,kk,id1
1537 character(len=128)msg
1538 integer::nfirenodes,nfireline
1539 real::sum_err,min_err,max_err,sum_aerr,min_aerr,max_aerr
1542 integer,parameter :: mstep=1000, printl=1
1543 real, parameter:: zero=0.,one=1.,eps=epsilon(zero),tol=100*eps, &
1544 safe=2.,rmin=safe*tiny(zero),rmax=huge(zero)/safe
1546 ! f90 intrinsic function
1548 intrinsic max,min,sqrt,nint,epsilon,tiny,huge
1552 !$OMP CRITICAL(SFIRE_CORE_CRIT)
1553 write(msg,'(a8,i5,a6,i5,3(a1,i5))')'prop_ls:',id,' tile ',its,':',ite,',',jts,':',jte
1554 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
1557 a=fire_back_weight ! from module_fr_sfire_util
1562 ihs2=max(its-2,ids) ! need lfn two beyond the tile but not outside the domain
1567 ihs=max(its-1,ids) ! compute tend one beyond the tile but not outside the domain
1573 call write_array_m(ihs,ihe,jhs,jhe,ims,ime,jms,jme,lfn_in,'lfn_in',id)
1576 ! check array dimensions
1577 call check_mesh_2dim(ihs2,ihe2,jhs2,jhe2,ims,ime,jms,jme)
1578 call print_2d_stats(ihs2,ihe2,jhs2,jhe2,ims,ime,jms,jme, &
1579 lfn_in,'prop_ls: lfn in')
1581 ! NOTE: tend_ls will extrapolate to one node strip at domain boundaries
1582 ! so that it can compute gradient at domain boundaries.
1583 ! To avoid copying, lfn_in is declared inout.
1584 ! At tile boundaries that are not domain boundaries values of lfn_in two nodes
1585 ! outside of the tile are needed.
1586 id1 = id ! for debug prints
1587 if(id1.ne.0)id1=id1+1000
1588 call tend_ls( id1, &
1589 ims,ime,jms,jme, & ! memory dims for lfn_in
1590 IMTS,IMTE,JMTS,JMTE, & ! memory dims for tend
1591 ids,ide,jds,jde, & ! domain dims - where lfn exists
1592 ips,ipe,jps,jpe, & ! patch - nodes owned by this process
1593 ihs,ihe,jhs,jhe, & ! where tend computed
1594 ims,ime,jms,jme, & ! memory dims for ros
1595 its,ite,jts,jte, & ! tile dims - where to set ros
1596 ts,dt,dx,dy, & ! scalars in
1597 lfn_in, & ! arrays in
1598 tbound, & ! scalars out
1599 tend, ros, & ! arrays out
1604 call write_array_m(ihs,ihe,jhs,jhe,IMTS,IMTE,JMTS,JMTE,tend,'tend1',id)
1607 ! Euler method, the half-step, same region as ted
1610 lfn1(i,j) = lfn_in(i,j) + dt*tend(i,j)
1614 call print_2d_stats(ihs,ihe,jhs,jhe,IMTS,IMTE,JMTS,JMTE, &
1615 lfn1,'prop_ls: lfn1')
1616 ! tend = F(lfn1) on the tile (not beyond)
1618 if(id1.ne.0)id1=id1+1000
1620 IMTS,IMTE,JMTS,JMTE, & ! memory dims for lfn
1621 IMTS,IMTE,JMTS,JMTE, & ! memory dims for tend
1622 ids,ide,jds,jde, & ! domain dims - where lfn exists
1623 ips,ipe,jps,jpe, & ! patch - nodes owned by this process
1624 its,ite,jts,jte, & ! tile dims - where is tend computed
1625 ims,ime,jms,jme, & ! memory dims for ros
1626 its,ite,jts,jte, & ! tile dims - where is ros set
1627 ts+dt,dt,dx,dy, & ! scalars in
1629 tbound2, & ! scalars out
1630 tend,ros, & ! arrays out
1635 call write_array_m(its,ite,jts,jte,IMTS,IMTE,JMTS,JMTE,tend,'tend2',id)
1638 call print_2d_stats(its,ite,jts,jte,IMTS,IMTE,JMTS,JMTE,tend,'prop_ls: tend2')
1640 tbound=min(tbound,tbound2)
1642 !$OMP CRITICAL(SFIRE_CORE_CRIT)
1643 write(msg,'(a,f10.2,4(a,f7.2))')'prop_ls: time',ts,' dt=',dt,' bound',min(tbound,999.99), &
1645 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
1648 !$OMP CRITICAL(SFIRE_CORE_CRIT)
1649 write(msg,'(2(a,f10.2))')'prop_ls: WARNING: time step ',dt, &
1651 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
1655 ! combine lfn1 and lfn_in + dt*tend -> lfn_out
1659 lfn_out(i,j) = a1*lfn1(i,j) + a*(lfn_in(i,j) + dt*tend(i,j))
1663 ! compute ignition time by interpolation
1664 ! the node was not burning at start but it is burning at end
1665 ! interpolate from the level functions at start and at end
1666 ! lfn_in is the level set function value at time ts
1667 ! lfn_out is the level set function value at time ts+dt
1668 ! 0 is the level set function value at time tign(i,j)
1669 ! thus assuming the level function is approximately linear =>
1670 ! tign(i,j)= ts + ((ts + td) - ts) * lfn_in / (lfn_in - lfn_out)
1671 ! = ts + dt * lfn_in / (lfn_in - lfn_out)
1674 time_now = time_now + abs(time_now)*epsilon(time_now)*2.
1677 ! interpolate the cross-over time
1678 if (.not. lfn_out(i,j)>0 .and. lfn_in(i,j)>0)then
1679 tign(i,j) = ts + dt * lfn_in(i,j) / (lfn_in(i,j) - lfn_out(i,j))
1681 ! set the ignition time outside of burning region
1682 if(lfn_out(i,j)>0.)tign(i,j)=time_now
1686 ! check local speed error and stats
1687 ! may not work correctly in parallel
1701 ! loop over right inside of the domain
1702 ! cannot use values outside of the domain, would have to reflect and that
1703 ! would change lfn_out
1704 ! cannot use values outside of tile, not synchronized yet
1705 ! so in parallel mode the statistics is not accurate
1708 if(lfn_out(i,j)>0.0)then ! a point out of burning region
1709 if(lfn_out(i+1,j)<=0.or.lfn_out(i,j+1)<=0.or. & ! neighbor in burning region
1710 lfn_out(i-1,j)<=0.or.lfn_out(i,j-1)<=0)then ! point next to fireline
1711 gradx=(lfn_out(i+1,j)-lfn_out(i-1,j))/(2.0*dx) ! central differences
1712 grady=(lfn_out(i,j+1)-lfn_out(i,j-1))/(2.0*dy)
1713 grad2=sqrt(gradx*gradx+grady*grady)
1714 aspeed = (lfn_in(i,j)-lfn_out(i,j))/(dt*max(grad2,rmin))
1715 rr = speed_func(gradx,grady,dx,dy,i,j,fp)
1718 min_err=min(min_err,err)
1719 max_err=max(max_err,err)
1721 sum_aerr=sum_aerr+aerr
1722 min_aerr=min(min_aerr,aerr)
1723 max_aerr=max(max_aerr,aerr)
1724 nfireline=nfireline+1
1727 nfirenodes=nfirenodes+1
1731 !$OMP CRITICAL(SFIRE_CORE_CRIT)
1732 write(msg,'(2(a,i6,f8.4))')'prop_ls: nodes burning',nfirenodes, &
1733 (100.*nfirenodes)/((ite1-its1+1)*(jte1-jts1+1)),'% next to fireline',nfireline
1734 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
1737 call print_stat_line('speed error',its1,ite1,jts1,jte1,min_err,max_err,sum_err/nfireline)
1738 call print_stat_line('abs(speed error)',its1,ite1,jts1,jte1,min_aerr,max_aerr,sum_aerr/nfireline)
1741 ! check if the fire did not get to the domain boundary
1743 ! do kk=1,(boundary_guard*(ide-ids+1))/100 ! in %
1744 do kk=1,boundary_guard ! measured in cells
1746 if(i.ge.its.and.i.le.ite)then
1748 if(lfn_out(i,j)<=0.)goto 9
1752 ! do kk=1,(boundary_guard*(jde-jds+1))/100
1753 do kk=1,boundary_guard ! measured in cells
1755 if(j.ge.jts.and.j.le.jte)then
1757 if(lfn_out(i,j)<=0.)goto 9
1764 !$OMP CRITICAL(SFIRE_CORE_CRIT)
1765 write(msg,'(a,i2,a,2i8)')'prop_ls: fire',boundary_guard, &
1766 ' cells from domain boundary at node ',i,j
1767 !$OMP END CRITICAL(SFIRE_CORE_CRIT)
1769 call crash('prop_ls: increase the fire region')
1772 call print_2d_stats(its,ite,jts,jte,ims,ime,jms,jme, &
1773 lfn_out,'prop_ls: lfn out')
1775 end subroutine prop_ls
1778 !*****************************
1781 subroutine tend_ls( id, &
1782 lims,lime,ljms,ljme, & ! memory dims for lfn
1783 tims,time,tjms,tjme, & ! memory dims for tend
1784 ids,ide,jds,jde, & ! domain - nodes where lfn defined
1785 ips,ipe,jps,jpe, & ! patch - nodes owned by this process
1786 ints,inte,jnts,jnte, & ! region - nodes where tend computed
1787 ims,ime,jms,jme, & ! memory dims for ros
1788 its,ite,jts,jte, & ! tile dims - where is ros set
1789 t,dt,dx,dy, & ! scalars in
1791 tbound, & ! scalars out
1792 tend, ros, & ! arrays out
1798 ! compute the right hand side of the level set equation
1801 integer,intent(in)::id,lims,lime,ljms,ljme,tims,time,tjms,tjme
1802 integer,intent(in)::ims,ime,jms,jme,its,ite,jts,jte
1803 integer, intent(in)::ids,ide,jds,jde,ints,inte,jnts,jnte,ips,ipe,jps,jpe
1804 real,intent(in)::t ! time
1805 real,intent(in)::dt,dx,dy ! mesh step
1806 real,dimension(lims:lime,ljms:ljme),intent(inout)::lfn ! level set function
1807 real,intent(out)::tbound ! max allowed time step
1808 real,dimension(tims:time,tjms:tjme),intent(out)::tend ! tendency (rhs of the level set pde)
1809 real,dimension(ims:ime,jms:jme),intent(out)::ros ! rate of spread
1810 type(fire_params),intent(in)::fp
1813 real:: te,diffLx,diffLy,diffRx,diffRy, &
1814 diffCx,diffCy,diff2x,diff2y,grad,rr, &
1815 ros_back,ros_wind,ros_slope,advx,advy,scale,nvx,nvy,speed,tanphi
1817 character(len=128)msg
1820 real, parameter:: eps=epsilon(0.0)
1822 real, parameter:: zero=0.,one=1.,tol=100*eps, &
1823 safe=2.,rmin=safe*tiny(zero),rmax=huge(zero)/safe
1826 ! f90 intrinsic function
1828 intrinsic max,min,sqrt,nint,tiny,huge
1832 real,dimension(tims:time,tjms:tjme)::rra,grada,speeda,tanphia
1837 ! check array dimensions
1838 call check_mesh_2dim(ints-1,inte+1,jnts-1,jnte+1,lims,lime,ljms,ljme)
1839 call check_mesh_2dim(ints,inte,jnts,jnte,tims,time,tjms,tjme)
1841 call continue_at_boundary(1,1,fire_lfn_ext_up, & !extend by extrapolation but never down
1842 lims,lime,ljms,ljme, & ! memory dims
1843 ids,ide,jds,jde, & ! domain - nodes where lfn defined
1844 ips,ipe,jps,jpe, & ! patch - nodes owned by this process
1845 ints,inte,jnts,jnte, & ! tile - nodes update by this thread
1848 call print_2d_stats(ints-1,inte+1,jnts,jnte,lims,lime,ljms,ljme, &
1849 lfn,'tend_ls: lfn cont dir x')
1850 call print_2d_stats(ints,inte,jnts-1,jnte+1,lims,lime,ljms,ljme, &
1851 lfn,'tend_ls: lfn cont dir y')
1854 call write_array_m(ints-1,inte+1,jnts-1,jnte+1,lims,lime,ljms,ljme,lfn,'tend_lfn_in',id)
1860 ! one sided differences
1861 diffRx = (lfn(i+1,j)-lfn(i,j))/dx
1862 diffLx = (lfn(i,j)-lfn(i-1,j))/dx
1863 diffRy = (lfn(i,j+1)-lfn(i,j))/dy
1864 diffLy = (lfn(i,j)-lfn(i,j-1))/dy
1865 diffCx = diffLx+diffRx ! TWICE CENTRAL DIFFERENCE
1866 diffCy = diffLy+diffRy
1868 !upwinding - select right or left derivative
1869 select case(fire_upwinding)
1871 grad=sqrt(diffCx**2 + diffCy**2)
1873 diff2x=select_upwind(diffLx,diffRx)
1874 diff2y=select_upwind(diffLy,diffRy)
1875 grad=sqrt(diff2x*diff2x + diff2y*diff2y)
1876 case(2) ! godunov per osher/fedkiw
1877 diff2x=select_godunov(diffLx,diffRx)
1878 diff2y=select_godunov(diffLy,diffRy)
1879 grad=sqrt(diff2x*diff2x + diff2y*diff2y)
1881 diff2x=select_eno(diffLx,diffRx)
1882 diff2y=select_eno(diffLy,diffRy)
1883 grad=sqrt(diff2x*diff2x + diff2y*diff2y)
1884 case(4) ! Sethian - twice stronger pushdown of bumps
1885 grad=sqrt(max(diffLx,0.)**2+min(diffRx,0.)**2 &
1886 + max(diffLy,0.)**2+min(diffRy,0.)**2)
1891 ! normal direction, from central differences
1892 scale=sqrt(diffCx*diffCx+diffCy*diffCy+eps)
1896 ! wind speed in direction of spread
1897 ! speed = vx(i,j)*nvx + vy(i,j)*nvy
1900 ! get rate of spread from wind speed and slope
1902 call fire_ros(ros_back,ros_wind,ros_slope, &
1905 rr=ros_back + ros_wind + ros_slope
1906 if(fire_grows_only.gt.0)rr=max(rr,0.)
1908 ! set ros for output
1909 if(i.ge.its.and.i.le.ite.and.j.ge.jts.and.j.le.jte)ros(i,j)=rr
1911 if(fire_upwind_split.eq.0)then
1913 ! get rate of spread
1914 te = -rr*grad ! normal term
1918 ! normal direction backing rate only
1919 te = - ros_back*grad
1921 ! advection in wind direction
1922 if (abs(speed)> eps) then
1923 advx=fp%vx(i,j)*ros_wind/speed
1924 advy=fp%vy(i,j)*ros_wind/speed
1930 ! tanphi = dzdxf*nvx + dzdyf*nvy
1931 ! advection from slope direction
1932 if(abs(tanphi)>eps) then
1933 advx=advx+fp%dzdxf(i,j)*ros_slope/tanphi
1934 advy=advy+fp%dzdyf(i,j)*ros_slope/tanphi
1937 if(fire_upwind_split.eq.1)then
1939 ! one-sided upwinding
1940 te = te - max(advx,0.)*diffLx - min(advx,0.)*diffRy &
1941 - max(advy,0.)*diffLy - min(advy,0.)*diffRy
1944 elseif(fire_upwind_split.eq.2)then
1947 call crash('prop_ls: bad fire_upwind_split, Lax-Friedrichs not done yet')
1951 call crash('prop_ls: bad fire_upwind_split')
1958 tbound = max(tbound,rr*(abs(diff2x)/dx+abs(diff2y)/dy)/grad)
1961 ! add numerical viscosity
1962 te=te + fire_viscosity*abs(rr)*((diffRx-diffLx)+(diffRy-diffLy))
1971 !write(msg,*)i,j,grad,dzdx,dzdy
1974 !if(abs(te)>1e4)then
1975 ! write(msg,'(a,2i5,e12.3)')'tend_ls: tend out of bounds at ',i,j,te
1982 call write_array_m(ints,inte,jnts,jnte,tims,time,tjms,tjme,rra,'rr',id)
1983 call write_array_m(ints,inte,jnts,jnte,tims,time,tjms,tjme,grada,'grad',id)
1984 call write_array_m(ints,inte,jnts,jnte,tims,time,tjms,tjme,speeda,'speed',id)
1985 call write_array_m(ints,inte,jnts,jnte,tims,time,tjms,tjme,tanphia,'tanphi',id)
1986 call write_array_m(ints,inte,jnts,jnte,tims,time,tjms,tjme,tend,'tend',id)
1989 call print_2d_stats(ints,inte,jnts,jnte,tims,time,tjms,tjme, &
1990 tend,'tend_ls: tend out')
1992 ! the final CFL bound
1993 tbound = 1/(tbound+tol)
1995 end subroutine tend_ls
1998 !**************************
2001 real function select_upwind(diffLx,diffRx)
2003 real, intent(in):: diffLx, diffRx
2006 ! upwind differences, L or R if bith same sign, otherwise zero
2009 if (diffLx>0.and.diffRx>0.)diff2x=diffLx
2010 if (diffLx<0.and.diffRx<0.)diff2x=diffRx
2012 select_upwind=diff2x
2013 end function select_upwind
2017 !**************************
2021 real function select_godunov(diffLx,diffRx)
2023 real, intent(in):: diffLx, diffRx
2026 ! Godunov scheme: upwind differences, L or R or none
2027 ! always test on > or < never = , much faster because of IEEE
2028 ! central diff >= 0 => take left diff if >0, ortherwise 0
2029 ! central diff <= 0 => take right diff if <0, ortherwise 0
2032 diffCx=diffRx+diffLx
2033 if (diffLx>0.and..not.diffCx<0)diff2x=diffLx
2034 if (diffRx<0.and. diffCx<0)diff2x=diffRx
2036 select_godunov=diff2x
2037 end function select_godunov
2040 !**************************
2043 real function select_eno(diffLx,diffRx)
2045 real, intent(in):: diffLx, diffRx
2048 ! 1st order ENO scheme
2050 if (.not.diffLx>0 .and. .not.diffRx>0)then
2052 elseif(.not.diffLx<0 .and. .not.diffRx<0)then
2054 elseif(.not.diffLx<0 .and. .not.diffRx>0)then
2055 if(.not. abs(diffRx) < abs(diffLx))then
2065 end function select_eno
2068 !**************************
2071 real function speed_func(diffCx,diffCy,dx,dy,i,j,fp)
2073 ! the level set method speed function
2076 real, intent(in)::diffCx,diffCy ! x and y coordinates of the direction of propagation
2077 real, intent(in)::dx,dy ! x and y coordinates of the direction of propagation
2078 integer, intent(in)::i,j ! indices of the node to compute the speed at
2079 type(fire_params),intent(in)::fp
2081 real::scale,nvx,nvy,r
2082 real::ros_back , ros_wind , ros_slope
2083 real, parameter:: eps=epsilon(0.0)
2085 ! normal direction, from central differences
2086 scale=sqrt(diffCx*diffCx+diffCy*diffCy+eps)
2090 ! get rate of spread from wind speed and slope
2092 call fire_ros(ros_back,ros_wind,ros_slope, &
2095 r=ros_back + ros_wind + ros_slope
2096 if(fire_grows_only.gt.0)r=max(r,0.)
2099 end function speed_func
2101 end module module_fr_sfire_core