Skip to content

Commit 2f41df8

Browse files
Copilotivan-pi
andauthored
Add Oregonator example with reference verification and work-precision output (#40)
* Add Oregonator example skeleton Agent-Logs-Url: https://github.com/ivan-pi/stiff3/sessions/f7b0e04d-83b2-4b6d-9b36-32da6a997174 Co-authored-by: ivan-pi <21085643+ivan-pi@users.noreply.github.com> * Finish Oregonator example implementation Agent-Logs-Url: https://github.com/ivan-pi/stiff3/sessions/f7b0e04d-83b2-4b6d-9b36-32da6a997174 Co-authored-by: ivan-pi <21085643+ivan-pi@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ivan-pi <21085643+ivan-pi@users.noreply.github.com>
1 parent cfc6c14 commit 2f41df8

3 files changed

Lines changed: 243 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,16 @@ stiff3.git = "https://github.com/ivan-pi/stiff3"
7474

7575
### Running the examples
7676

77-
Six examples called `robertson`, `vanpol`, `lorenz`, `predator_prey`, `pendant_drop`, and `three_equation_system` are provided. They can be run with the command
77+
Seven examples called `robertson`, `vanpol`, `lorenz`, `predator_prey`, `pendant_drop`, `three_equation_system`, and `oregonator` are provided. They can be run with the command
7878

7979
```sh
8080
fpm run --example <name>
8181
```
8282

8383
With CMake, the compiled executables are placed in the `build/example/` directory and can be run directly or via CTest.
8484

85+
The `oregonator` example verifies a published reference solution at `t = 360` and writes `oregonator_work_precision.csv` together with an `oregonator_work_precision.svg` plot in the current working directory.
86+
8587
## Usage
8688

8789
Basic use of the solver is demonstrated using the [Van der Pol oscillator](https://en.wikipedia.org/wiki/Van_der_Pol_oscillator):

example/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
foreach(example robertson vanpol lorenz predator_prey pendant_drop three_equation_system)
1+
foreach(example robertson vanpol lorenz predator_prey pendant_drop three_equation_system oregonator)
22
add_executable(${example} ${example}.f90)
33
target_link_libraries(${example} PRIVATE stiff3)
44
add_test(NAME ${example} COMMAND ${example})

example/oregonator.f90

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
program oregonator
2+
3+
use stiff3_solver, only: stiff3, wp => stiff3_wp
4+
5+
implicit none
6+
7+
integer, parameter :: n = 3
8+
integer, parameter :: ncases = 6
9+
real(wp), parameter :: y0(n) = [1.0_wp, 2.0_wp, 3.0_wp]
10+
real(wp), parameter :: yref(n) = [ &
11+
1.000814870318523_wp, 1228.178521549917_wp, 132.0554942846706_wp ]
12+
real(wp), parameter :: x0 = 0.0_wp
13+
real(wp), parameter :: x1 = 360.0_wp
14+
real(wp), parameter :: h0_initial = 1.0e-3_wp
15+
real(wp), parameter :: verification_tol = 1.0e-8_wp
16+
real(wp), parameter :: eps_values(ncases) = [ &
17+
1.0e-4_wp, 1.0e-5_wp, 1.0e-6_wp, 1.0e-7_wp, 1.0e-8_wp, 1.0e-9_wp ]
18+
character(len=*), parameter :: csv_file = 'oregonator_work_precision.csv'
19+
character(len=*), parameter :: svg_file = 'oregonator_work_precision.svg'
20+
21+
integer :: i
22+
integer :: verification_stats(6)
23+
integer :: stats(6,ncases)
24+
real(wp) :: y(n), w(n), errors(ncases), work(ncases), verification_error
25+
26+
w = 1.0_wp
27+
28+
call integrate_case(1.0e-8_wp, y, verification_stats)
29+
verification_error = max_relative_error(y, yref)
30+
if (verification_error > verification_tol) then
31+
print '(A,ES12.4,A,ES12.4)', &
32+
'reference verification failed: max relative error=', verification_error, &
33+
' exceeds ', verification_tol
34+
error stop 1
35+
end if
36+
37+
print '(A,ES12.4)', 'verified reference solution with max relative error ', verification_error
38+
print '(A,3(1X,ES24.16))', 'y(360) =', y
39+
print '(A,6(I0,1X))', 'verification stats [nacc nrej nfev njev nlu nsol]: ', verification_stats
40+
41+
do i = 1, ncases
42+
call integrate_case(eps_values(i), y, stats(:,i))
43+
errors(i) = max_relative_error(y, yref)
44+
work(i) = real(stats(3,i), wp)
45+
end do
46+
47+
call write_csv(csv_file, eps_values, errors, stats)
48+
call write_svg(svg_file, eps_values, errors, work)
49+
50+
print '(A)', 'work-precision data:'
51+
print '(A)', ' eps error nfev njev nlu nsol'
52+
do i = 1, ncases
53+
print '(2(1X,ES10.2),4(1X,I6))', eps_values(i), errors(i), &
54+
stats(3,i), stats(4,i), stats(5,i), stats(6,i)
55+
end do
56+
print '(A,1X,A)', 'wrote', csv_file
57+
print '(A,1X,A)', 'wrote', svg_file
58+
59+
contains
60+
61+
subroutine integrate_case(eps, y, stats)
62+
real(wp), intent(in) :: eps
63+
real(wp), intent(out) :: y(n)
64+
integer, intent(out) :: stats(6)
65+
66+
real(wp) :: h0
67+
68+
y = y0
69+
h0 = h0_initial
70+
call stiff3(n, fun, x0, y, x1, jac, h0, eps, w, stats=stats)
71+
end subroutine
72+
73+
function max_relative_error(y, y_reference) result(err)
74+
real(wp), intent(in) :: y(n), y_reference(n)
75+
real(wp) :: err
76+
77+
err = maxval(abs(y - y_reference)/max(1.0_wp, abs(y_reference)))
78+
end function
79+
80+
subroutine write_csv(filename, eps, err, stats)
81+
character(len=*), intent(in) :: filename
82+
real(wp), intent(in) :: eps(:), err(:)
83+
integer, intent(in) :: stats(:,:)
84+
85+
integer :: i, ios, unit
86+
87+
open(newunit=unit, file=filename, status='replace', action='write', iostat=ios)
88+
if (ios /= 0) error stop 'failed to open CSV output file'
89+
90+
write(unit,'(A)') 'eps,max_relative_error,nfev,njev,nlu,nsol,nacc,nrej'
91+
do i = 1, size(eps)
92+
write(unit,'(ES24.16,'','',ES24.16,'','',I0,'','',I0,'','',I0,'','',I0,'','',I0,'','',I0)') &
93+
eps(i), err(i), stats(3,i), stats(4,i), stats(5,i), stats(6,i), stats(1,i), stats(2,i)
94+
end do
95+
96+
close(unit)
97+
end subroutine
98+
99+
subroutine write_svg(filename, eps, err, work)
100+
character(len=*), intent(in) :: filename
101+
real(wp), intent(in) :: eps(:), err(:), work(:)
102+
103+
integer, parameter :: width = 800
104+
integer, parameter :: height = 600
105+
real(wp), parameter :: left = 100.0_wp
106+
real(wp), parameter :: right = 30.0_wp
107+
real(wp), parameter :: top = 50.0_wp
108+
real(wp), parameter :: bottom = 80.0_wp
109+
110+
character(len=32) :: label
111+
integer :: i, ios, unit
112+
real(wp) :: log_work(size(work)), log_err(size(err))
113+
real(wp) :: xmin, xmax, ymin, ymax, xpad, ypad
114+
real(wp) :: x1p, x2p, y1p, y2p, xtick, ytick, tick_value
115+
116+
log_work = log10(max(work, tiny(1.0_wp)))
117+
log_err = log10(max(err, tiny(1.0_wp)))
118+
119+
xmin = minval(log_work)
120+
xmax = maxval(log_work)
121+
ymin = minval(log_err)
122+
ymax = maxval(log_err)
123+
xpad = 0.05_wp*max(xmax - xmin, 1.0_wp)
124+
ypad = 0.08_wp*max(ymax - ymin, 1.0_wp)
125+
xmin = xmin - xpad
126+
xmax = xmax + xpad
127+
ymin = ymin - ypad
128+
ymax = ymax + ypad
129+
130+
open(newunit=unit, file=filename, status='replace', action='write', iostat=ios)
131+
if (ios /= 0) error stop 'failed to open SVG output file'
132+
133+
write(unit,'(A)') '<?xml version="1.0" encoding="UTF-8"?>'
134+
write(unit,'(A,I0,A,I0,A)') '<svg xmlns="http://www.w3.org/2000/svg" width="', width, &
135+
'" height="', height, '" viewBox="0 0 800 600">'
136+
write(unit,'(A)') '<rect width="100%" height="100%" fill="white"/>'
137+
write(unit,'(A)') &
138+
'<text x="400" y="28" font-family="sans-serif" font-size="22" ' // &
139+
'text-anchor="middle">Oregonator work-precision diagram</text>'
140+
write(unit,'(A,F0.3,A,F0.3,A,F0.3,A,F0.3,A)') &
141+
'<rect x="', left, '" y="', top, '" width="', real(width,wp) - left - right, &
142+
'" height="', real(height,wp) - top - bottom, '" fill="none" stroke="black" stroke-width="1.5"/>'
143+
144+
do i = 0, 4
145+
xtick = left + real(i,wp)*(real(width,wp) - left - right)/4.0_wp
146+
tick_value = 10.0_wp**(xmin + real(i,wp)*(xmax - xmin)/4.0_wp)
147+
write(label,'(ES10.2)') tick_value
148+
write(unit,'(A,F0.3,A,F0.3,A)') '<line x1="', xtick, '" y1="520" x2="', xtick, &
149+
'" y2="526" stroke="#666"/>'
150+
write(unit,'(A,F0.3,A)') '<text x="', xtick, '" y="548" font-family="sans-serif" font-size="12" text-anchor="middle">'
151+
write(unit,'(A)') trim(adjustl(label))//'</text>'
152+
end do
153+
154+
do i = 0, 4
155+
ytick = real(height,wp) - bottom - real(i,wp)*(real(height,wp) - top - bottom)/4.0_wp
156+
tick_value = 10.0_wp**(ymin + real(i,wp)*(ymax - ymin)/4.0_wp)
157+
write(label,'(ES10.2)') tick_value
158+
write(unit,'(A,F0.3,A,F0.3,A)') '<line x1="94" y1="', ytick, '" x2="100" y2="', ytick, &
159+
'" stroke="#666"/>'
160+
write(unit,'(A,F0.3,A)') '<text x="84" y="', ytick + 4.0_wp, '" font-family="sans-serif" font-size="12" text-anchor="end">'
161+
write(unit,'(A)') trim(adjustl(label))//'</text>'
162+
end do
163+
164+
write(unit,'(A)') &
165+
'<text x="435" y="585" font-family="sans-serif" font-size="16" ' // &
166+
'text-anchor="middle">rhs evaluations (nfev)</text>'
167+
write(unit,'(A)') &
168+
'<text x="24" y="285" font-family="sans-serif" font-size="16" ' // &
169+
'text-anchor="middle" transform="rotate(-90 24 285)">' // &
170+
'max relative error</text>'
171+
172+
do i = 1, size(work) - 1
173+
x1p = xcoord(log_work(i), xmin, xmax, width, left, right)
174+
y1p = ycoord(log_err(i), ymin, ymax, height, top, bottom)
175+
x2p = xcoord(log_work(i + 1), xmin, xmax, width, left, right)
176+
y2p = ycoord(log_err(i + 1), ymin, ymax, height, top, bottom)
177+
write(unit,'(A,F0.3,A,F0.3,A,F0.3,A,F0.3,A)') '<line x1="', x1p, '" y1="', y1p, &
178+
'" x2="', x2p, '" y2="', y2p, '" stroke="#1f77b4" stroke-width="2"/>'
179+
end do
180+
181+
do i = 1, size(work)
182+
x1p = xcoord(log_work(i), xmin, xmax, width, left, right)
183+
y1p = ycoord(log_err(i), ymin, ymax, height, top, bottom)
184+
write(label,'(ES9.1)') eps(i)
185+
write(unit,'(A,F0.3,A,F0.3,A)') '<circle cx="', x1p, '" cy="', y1p, '" r="4.5" fill="#d62728"/>'
186+
write(unit,'(A,F0.3,A,F0.3,A)') '<text x="', x1p + 8.0_wp, '" y="', y1p - 8.0_wp, &
187+
'" font-family="sans-serif" font-size="12">'
188+
write(unit,'(A)') 'eps='//trim(adjustl(label))//'</text>'
189+
end do
190+
191+
write(unit,'(A)') '</svg>'
192+
193+
close(unit)
194+
195+
end subroutine
196+
197+
function xcoord(value, xmin, xmax, width, left, right) result(xplot)
198+
real(wp), intent(in) :: value, xmin, xmax, left, right
199+
integer, intent(in) :: width
200+
real(wp) :: xplot
201+
202+
xplot = left + (value - xmin)*(real(width,wp) - left - right)/(xmax - xmin)
203+
end function
204+
205+
function ycoord(value, ymin, ymax, height, top, bottom) result(yplot)
206+
real(wp), intent(in) :: value, ymin, ymax, top, bottom
207+
integer, intent(in) :: height
208+
real(wp) :: yplot
209+
210+
yplot = real(height,wp) - bottom - &
211+
(value - ymin)*(real(height,wp) - top - bottom)/(ymax - ymin)
212+
end function
213+
214+
subroutine fun(n, y, f)
215+
integer, intent(in) :: n
216+
real(wp), intent(in) :: y(n)
217+
real(wp), intent(inout) :: f(n)
218+
219+
f(1) = 77.27_wp*(y(2) + y(1)*(1.0_wp - 8.375e-6_wp*y(1) - y(2)))
220+
f(2) = (y(3) - (1.0_wp + y(1))*y(2))/77.27_wp
221+
f(3) = 0.161_wp*(y(1) - y(3))
222+
end subroutine
223+
224+
subroutine jac(n, y, df)
225+
integer, intent(in) :: n
226+
real(wp), intent(in) :: y(n)
227+
real(wp), intent(inout) :: df(n,n)
228+
229+
df = 0.0_wp
230+
df(1,1) = 77.27_wp*(1.0_wp - 2.0_wp*8.375e-6_wp*y(1) - y(2))
231+
df(1,2) = 77.27_wp*(1.0_wp - y(1))
232+
df(2,1) = -y(2)/77.27_wp
233+
df(2,2) = -(1.0_wp + y(1))/77.27_wp
234+
df(2,3) = 1.0_wp/77.27_wp
235+
df(3,1) = 0.161_wp
236+
df(3,3) = -0.161_wp
237+
end subroutine
238+
239+
end program

0 commit comments

Comments
 (0)