Skip to content

Commit f716fd1

Browse files
Copilotawvwgk
andauthored
Switch to V3K connection table format for 1000+ atoms or bonds (grimme-lab#107)
Co-authored-by: awvwgk <28669218+awvwgk@users.noreply.github.com>
1 parent 326ebc1 commit f716fd1

3 files changed

Lines changed: 253 additions & 2 deletions

File tree

src/mctc/io/write/ctfile.f90

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module mctc_io_write_ctfile
2121

2222
public :: write_molfile, write_sdf
2323

24+
!> Maximum number of atoms/bonds supported by V2000 format
25+
integer, parameter :: v2k_max = 999
26+
2427

2528
contains
2629

@@ -57,7 +60,27 @@ subroutine write_sdf(self, unit, energy, gnorm)
5760
end subroutine write_sdf
5861

5962

63+
!> Write molecular structure data to a molfile
6064
subroutine write_molfile(self, unit, comment_line)
65+
!> Instance of the molecular structure data
66+
class(structure_type), intent(in) :: self
67+
!> File handle
68+
integer, intent(in) :: unit
69+
!> Optional comment line
70+
character(len=*), intent(in), optional :: comment_line
71+
72+
! Switch to V3000 format if we exceed V2000 limits
73+
if (self%nat > v2k_max .or. self%nbd > v2k_max) then
74+
call write_molfile_v3k(self, unit, comment_line)
75+
else
76+
call write_molfile_v2k(self, unit, comment_line)
77+
end if
78+
79+
end subroutine write_molfile
80+
81+
82+
!> Write molecular structure data to a V2000 molfile
83+
subroutine write_molfile_v2k(self, unit, comment_line)
6184
class(structure_type), intent(in) :: self
6285
integer, intent(in) :: unit
6386
character(len=*), intent(in), optional :: comment_line
@@ -129,7 +152,88 @@ subroutine write_molfile(self, unit, comment_line)
129152

130153
write(unit, '(a)') "M END"
131154

132-
end subroutine write_molfile
155+
end subroutine write_molfile_v2k
156+
157+
158+
!> Write molecular structure data to a V3000 molfile
159+
subroutine write_molfile_v3k(self, unit, comment_line)
160+
class(structure_type), intent(in) :: self
161+
integer, intent(in) :: unit
162+
character(len=*), intent(in), optional :: comment_line
163+
integer :: iatom, ibond, btype
164+
logical :: has_sdf_data
165+
character(len=8) :: date
166+
character(len=10) :: time
167+
character(len=256) :: line
168+
169+
call date_and_time(date, time)
170+
171+
! Header block (3 lines)
172+
if (present(comment_line)) then
173+
write(unit, '(a)') comment_line
174+
else
175+
if (allocated(self%comment)) then
176+
write(unit, '(a)') self%comment
177+
else
178+
write(unit, '(a)')
179+
end if
180+
end if
181+
write(unit, '(2x, 3x, 5x, 3a2, a4, "3D")') &
182+
& date(5:6), date(7:8), date(3:4), time(:4)
183+
write(unit, '(a)')
184+
185+
! Counts line for V3000 (atoms and bonds are set to 0 in header)
186+
write(unit, '(a)') " 0 0 0 0 0 999 V3000"
187+
188+
! V3000 block
189+
write(unit, '(a)') "M V30 BEGIN CTAB"
190+
write(unit, '(a, i0, a, i0, a)') "M V30 COUNTS ", self%nat, " ", self%nbd, " 0 0 0"
191+
192+
has_sdf_data = allocated(self%sdf)
193+
194+
! Atom block
195+
write(unit, '(a)') "M V30 BEGIN ATOM"
196+
do iatom = 1, self%nat
197+
! Build basic atom line: index, symbol, x, y, z, aamap (0)
198+
write(line, '(a, i0, a, a, 3(a, f0.6), a)') &
199+
& "M V30 ", iatom, " ", trim(self%sym(self%id(iatom))), &
200+
& " ", self%xyz(1, iatom)*autoaa, &
201+
& " ", self%xyz(2, iatom)*autoaa, &
202+
& " ", self%xyz(3, iatom)*autoaa, " 0"
203+
204+
! Add optional properties
205+
if (has_sdf_data) then
206+
if (self%sdf(iatom)%charge /= 0) then
207+
write(line, '(a, a, i0)') trim(line), " CHG=", self%sdf(iatom)%charge
208+
end if
209+
if (self%sdf(iatom)%valence /= 0) then
210+
write(line, '(a, a, i0)') trim(line), " VAL=", self%sdf(iatom)%valence
211+
end if
212+
end if
213+
214+
write(unit, '(a)') trim(line)
215+
end do
216+
write(unit, '(a)') "M V30 END ATOM"
217+
218+
! Bond block
219+
if (self%nbd > 0) then
220+
write(unit, '(a)') "M V30 BEGIN BOND"
221+
do ibond = 1, self%nbd
222+
if (size(self%bond, 1) > 2) then
223+
btype = self%bond(3, ibond)
224+
else
225+
btype = 1
226+
end if
227+
write(unit, '(a, i0, a, i0, a, i0, a, i0)') &
228+
& "M V30 ", ibond, " ", btype, " ", self%bond(1, ibond), " ", self%bond(2, ibond)
229+
end do
230+
write(unit, '(a)') "M V30 END BOND"
231+
end if
232+
233+
write(unit, '(a)') "M V30 END CTAB"
234+
write(unit, '(a)') "M END"
235+
236+
end subroutine write_molfile_v3k
133237

134238

135239
end module mctc_io_write_ctfile

subprojects/.wraplock

Whitespace-only changes.

test/test_write_ctfile.f90

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
! limitations under the License.
1414

1515
module test_write_ctfile
16+
use mctc_env_accuracy, only : wp
1617
use mctc_env_testing, only : new_unittest, unittest_type, error_type, check
1718
use testsuite_structure, only : get_structure
1819
use mctc_io_write_ctfile
@@ -35,7 +36,10 @@ subroutine collect_write_ctfile(testsuite)
3536

3637
testsuite = [ &
3738
& new_unittest("valid1-mol", test_valid1_mol), &
38-
& new_unittest("valid1-sdf", test_valid1_sdf) &
39+
& new_unittest("valid1-sdf", test_valid1_sdf), &
40+
& new_unittest("v3k-large-mol", test_v3k_large_mol), &
41+
& new_unittest("v3k-large-sdf", test_v3k_large_sdf), &
42+
& new_unittest("v3k-with-bonds", test_v3k_with_bonds) &
3943
& ]
4044

4145
end subroutine collect_write_ctfile
@@ -97,4 +101,147 @@ subroutine test_valid1_sdf(error)
97101
end subroutine test_valid1_sdf
98102

99103

104+
subroutine test_v3k_large_mol(error)
105+
106+
!> Error handling
107+
type(error_type), allocatable, intent(out) :: error
108+
109+
type(structure_type) :: struc, struc_read
110+
integer :: unit, nat, i
111+
character(len=2), allocatable :: sym(:)
112+
real(wp), allocatable :: xyz(:, :)
113+
114+
! Create a structure with 1001 atoms to trigger V3000 format
115+
nat = 1001
116+
allocate(sym(nat), xyz(3, nat))
117+
118+
! Create alternating H and C atoms on a line
119+
do i = 1, nat
120+
if (mod(i, 2) == 0) then
121+
sym(i) = "C"
122+
else
123+
sym(i) = "H"
124+
end if
125+
xyz(1, i) = real(i, wp)
126+
xyz(2, i) = 0.0_wp
127+
xyz(3, i) = 0.0_wp
128+
end do
129+
130+
call new(struc, sym, xyz)
131+
132+
open(status='scratch', newunit=unit)
133+
call write_molfile(struc, unit)
134+
rewind(unit)
135+
136+
call read_molfile(struc_read, unit, error)
137+
close(unit)
138+
if (allocated(error)) return
139+
140+
call check(error, struc_read%nat, nat, "Number of atoms does not match")
141+
if (allocated(error)) return
142+
call check(error, struc_read%nid, 2, "Number of species does not match")
143+
if (allocated(error)) return
144+
145+
end subroutine test_v3k_large_mol
146+
147+
148+
subroutine test_v3k_large_sdf(error)
149+
150+
!> Error handling
151+
type(error_type), allocatable, intent(out) :: error
152+
153+
type(structure_type) :: struc, struc_read
154+
integer :: unit, nat, i
155+
character(len=2), allocatable :: sym(:)
156+
real(wp), allocatable :: xyz(:, :)
157+
158+
! Create a structure with 1001 atoms to trigger V3000 format
159+
nat = 1001
160+
allocate(sym(nat), xyz(3, nat))
161+
162+
! Create alternating H and C atoms on a line
163+
do i = 1, nat
164+
if (mod(i, 2) == 0) then
165+
sym(i) = "C"
166+
else
167+
sym(i) = "H"
168+
end if
169+
xyz(1, i) = real(i, wp)
170+
xyz(2, i) = 0.0_wp
171+
xyz(3, i) = 0.0_wp
172+
end do
173+
174+
call new(struc, sym, xyz)
175+
176+
open(status='scratch', newunit=unit)
177+
call write_sdf(struc, unit)
178+
rewind(unit)
179+
180+
call read_sdf(struc_read, unit, error)
181+
close(unit)
182+
if (allocated(error)) return
183+
184+
call check(error, struc_read%nat, nat, "Number of atoms does not match")
185+
if (allocated(error)) return
186+
call check(error, struc_read%nid, 2, "Number of species does not match")
187+
if (allocated(error)) return
188+
189+
end subroutine test_v3k_large_sdf
190+
191+
192+
subroutine test_v3k_with_bonds(error)
193+
194+
!> Error handling
195+
type(error_type), allocatable, intent(out) :: error
196+
197+
type(structure_type) :: struc, struc_read
198+
integer :: unit, nat, nbd, i
199+
character(len=2), allocatable :: sym(:)
200+
real(wp), allocatable :: xyz(:, :)
201+
integer, allocatable :: bond(:, :)
202+
203+
! Create a structure with 1001 atoms and 1000 bonds to trigger V3000 format
204+
nat = 1001
205+
nbd = 1000
206+
allocate(sym(nat), xyz(3, nat), bond(3, nbd))
207+
208+
! Create a linear chain of alternating H and C atoms
209+
do i = 1, nat
210+
if (mod(i, 2) == 0) then
211+
sym(i) = "C"
212+
else
213+
sym(i) = "H"
214+
end if
215+
xyz(1, i) = real(i, wp)
216+
xyz(2, i) = 0.0_wp
217+
xyz(3, i) = 0.0_wp
218+
end do
219+
220+
! Create bonds connecting each consecutive atom pair
221+
do i = 1, nbd
222+
bond(1, i) = i
223+
bond(2, i) = i + 1
224+
bond(3, i) = 1 ! single bond
225+
end do
226+
227+
call new(struc, sym, xyz, bond=bond)
228+
229+
open(status='scratch', newunit=unit)
230+
call write_molfile(struc, unit)
231+
rewind(unit)
232+
233+
call read_molfile(struc_read, unit, error)
234+
close(unit)
235+
if (allocated(error)) return
236+
237+
call check(error, struc_read%nat, nat, "Number of atoms does not match")
238+
if (allocated(error)) return
239+
call check(error, struc_read%nid, 2, "Number of species does not match")
240+
if (allocated(error)) return
241+
call check(error, struc_read%nbd, nbd, "Number of bonds does not match")
242+
if (allocated(error)) return
243+
244+
end subroutine test_v3k_with_bonds
245+
246+
100247
end module test_write_ctfile

0 commit comments

Comments
 (0)