-
Notifications
You must be signed in to change notification settings - Fork 3
/
archive.jl
374 lines (321 loc) · 13.3 KB
/
archive.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
## Handling of the topological archive internally used to recognize topologies.
using Pkg.Artifacts
const CRYSTALNETS_ARCHIVE_VERSION = "2"
const arc_location = artifact"archives"
"""
const CRYSTALNETS_ARCHIVE::Dict{String,String}
The archive used to recognize known topologies.
You probably don't need to access it directly: rely on [`recognize_topology`](@ref) to read
and the various archive functions like [`add_to_current_archive!`](@ref) to write.
"""
const CRYSTALNETS_ARCHIVE = if isdir(arc_location) && !isempty(readdir(arc_location))
flag, parsed = parse_arcs(arc_location)
if !flag
error("""CrystalNets.jl appears to have a broken installation (incompatible archive version).
Please rebuild CrystalNets.jl with `import Pkg; Pkg.build("CrystalNets")`.
""")
end
parsed
else
error("""CrystalNets.jl appears to have a broken installation (missing default archive).
Please rebuild CrystalNets.jl with `import Pkg; Pkg.build("CrystalNets")`.
This issue may be due to an error while using `CrystalNets.clean_default_archive!(...)`.
If you have not encountered any such error and you did not modify or erase the archive located at $arc_location please open an issue at https://github.com/coudertlab/CrystalNets.jl/issues/new
""")
end
"""
const REVERSE_CRYSTALNETS_ARCHIVE::Dict{String,String}
Reverse of [`CRYSTALNETS_ARCHIVE`](@ref).
Can be used to query the topological genome of known nets, as in:
```jldoctest
julia> REVERSE_CRYSTALNETS_ARCHIVE["dia"]
"3 1 2 0 0 0 1 2 0 0 1 1 2 0 1 0 1 2 1 0 0"
julia> topological_genome(CrystalNet(PeriodicGraph(ans)))
dia
```
!!! note
It is also possible to directly access the topological genome as a `PeriodicGraph`
by parsing the name as a [`TopologicalGenome`](@ref):
```jldoctest
julia> parse(TopologicalGenome, "pcu").genome
PeriodicGraph3D(1, PeriodicEdge3D[(1, 1, (0,0,1)), (1, 1, (0,1,0)), (1, 1, (1,0,0))])
julia> string(parse(TopologicalGenome, "nbo").genome) == REVERSE_CRYSTALNETS_ARCHIVE["nbo"]
true
```
"""
const REVERSE_CRYSTALNETS_ARCHIVE = Dict{String,String}(id => (startswith(key, "unstable") ? key[10:end] : key) for (key, id) in CRYSTALNETS_ARCHIVE)
export clean_default_archive!,
set_default_archive!,
empty_default_archive!,
change_current_archive!,
refresh_current_archive!,
add_to_current_archive!,
make_archive,
REVERSE_CRYSTALNETS_ARCHIVE
function _reset_archive!()
global CRYSTALNETS_ARCHIVE
global REVERSE_CRYSTALNETS_ARCHIVE
global arc_location
empty!(CRYSTALNETS_ARCHIVE)
merge!(CRYSTALNETS_ARCHIVE, last(parse_arcs(arc_location)))
empty!(REVERSE_CRYSTALNETS_ARCHIVE)
merge!(REVERSE_CRYSTALNETS_ARCHIVE, Dict{String,String}(last(x) => first(x) for x in CRYSTALNETS_ARCHIVE))
nothing
end
function validate_archive(custom_arc, avoid_recompute=true)::Dict{String,String}
arc = try
Serialization.deserialize(custom_arc)
catch
nothing
end
if arc isa Dict{String,String} && !isempty(arc) && isnumeric(first(first(keys(arc))))
@ifwarn @info "Processing input as a serialized CrystalNets.jl archive"
else
try
flag, parsed = parse_arc(custom_arc)
if flag && avoid_recompute
arc = parsed
else
@ifwarn begin
@info "Processing input as a topological .arc file"
if occursin("Made by CrystalNets.jl", readline(custom_arc))
@info "This archive was generated by an older version of CrystalNets."
end
@info "Keys will be converted to the topological genome used by CrystalNets. This may take a while."
end
arc_per_thread = [Pair{String,String}[] for _ in 1:nthreads()]
Threads.@threads for (key, id) in collect(parsed)
_g = topological_genome(CrystalNet(PeriodicGraph(key)))
if _g.unstable || !isempty(_g.error)
if _g.unstable
println(stderr, "Net ", id, " is unstable.")
else
println(stderr, "Failed for net ", id, " with error:", g.error)
end
continue
end
genome = string(_g.genome)
if !isempty(genome)
push!(arc_per_thread[threadid()], (genome => id))
end
end
arc = Dict{String,String}(pop!(arc_per_thread))
for _arc in arc_per_thread
merge!(arc, Dict{String,String}(_arc))
end
end
catch e
print(stderr, """
Impossible to parse input as a topological archive. Please use a format
similar to that of the RCSR Systre .arc file, with for each entry at
least the "id" and "key" fields.
This error may also occur if the given archive was empty. If you
wish to set an empty archive, use `CrystalNets.empty_default_archive!()`
""", '\n', "Encountered error: ")
rethrow()
# showerror(stderr, e)
# Base.display_error(stderr, e, catch_backtrace())
end
end
return arc
end
"""
clean_default_archive!(custom_arc; validate=true, refresh=true)
Erase the default archive used by CrystalNets.jl to recognize known topologies
and replace it with a new one from the file located at `custom_arc`.
The `validate` parameter controls whether the new file is checked and converted
to a format usable by CrystalNets.jl. If unsure, leave it set.
The `refresh` optional parameter controls whether the current archive should be
replaced by the new default one.
!!! warning
This archive will be kept and used for subsequent runs of CrystalNets.jl, even
if you restart your Julia session.
To only change the archive for the current session, use [`change_current_archive!(custom_arc)`](@ref change_current_archive!).
See also [`refresh_current_archive!`](@ref) for similar uses.
!!! warning
The previous default archive cannot be recovered afterwards, so make sure to
keep a copy if necessary. The default archive is the set of ".arc" files located
at `joinpath(dirname(dirname(pathof(CrystalNets))), "archives")`.
"""
function clean_default_archive!(custom_arc=nothing; validate=true, refresh=true, name="new")
rm(arc_location; recursive=true)
mkdir(arc_location)
if validate
arc = validate_archive(custom_arc)
export_arc(joinpath(arc_location, name*".arc"), false, arc)
else
cp(custom_arc, arc_location)
end
if refresh
refresh_current_archive!()
end
nothing
end
"""
set_default_archive!()
Set the current archive as the new default archive.
!!! warning
This archive will be kept and used for subsequent runs of CrystalNets.jl, even
if you restart your Julia session.
"""
function set_default_archive!(name="new")
global CRYSTALNETS_ARCHIVE
export_arc(joinpath(arc_location, name*".arc"))
end
"""
empty_default_archive!(; refresh=true)
Empty the default archive. This will prevent CrystalNets from recognizing any
topology before they are explicitly added.
The `refresh` optional parameter controls whether the current archive should also
be emptied.
!!! warning
This empty archive will be kept and used for subsequent runs of CrystalNets.jl, even
if you restart your Julia session. If you only want to empty the current archive,
do `empty!(CrystalNets.CRYSTALNETS_ARCHIVE)`.
"""
function empty_default_archive!(; refresh=true)
global CRYSTALNETS_ARCHIVE
global REVERSE_CRYSTALNETS_ARCHIVE
export_arc(arc_location, true)
if refresh
empty!(CRYSTALNETS_ARCHIVE)
empty!(REVERSE_CRYSTALNETS_ARCHIVE)
end
nothing
end
function _change_current_archive!(newarc)
global CRYSTALNETS_ARCHIVE
global REVERSE_CRYSTALNETS_ARCHIVE
empty!(CRYSTALNETS_ARCHIVE)
empty!(REVERSE_CRYSTALNETS_ARCHIVE)
merge!(CRYSTALNETS_ARCHIVE, newarc)
merge!(REVERSE_CRYSTALNETS_ARCHIVE,
Dict{String,String}(last(x) => first(x) for x in CRYSTALNETS_ARCHIVE))
nothing
end
"""
change_current_archive!(custom_arc; validate=true)
Erase the current archive used by CrystalNets.jl to recognize known topologies and
replace it with the archive stored in the file located at `custom_arc`.
The `validate` optional parameter controls whether the new file is checked and converted
to a format usable by CrystalNets.jl. If unsure, leave it set.
!!! note
This modification will only last for the duration of this Julia session.
If you wish to change the default archive and use it for subsequent runs, use
[`clean_default_archive!`](@ref).
!!! warning
Using an invalid archive will make CrystalNets.jl unusable. If this happens,
simply run [`refresh_current_archive!()`](@ref) to revert to the
default archive.
"""
function change_current_archive!(custom_arc; validate=true)
arc::Dict{String,String} = if validate
validate_archive(custom_arc)
else
last(parse_arc(custom_arc))
end
_change_current_archive!(arc)
end
"""
refresh_current_archive!()
Revert the current topological archive to the default one.
"""
function refresh_current_archive!()
_change_current_archive!(last(parse_arcs(arc_location)))
end
function _update_archive!(id, genome)
global CRYSTALNETS_ARCHIVE
global REVERSE_CRYSTALNETS_ARCHIVE
CRYSTALNETS_ARCHIVE[genome] = id
REVERSE_CRYSTALNETS_ARCHIVE[id] = genome
nothing
end
"""
add_to_current_archive!(id, genome)
Mark `genome` as the topological genome associated with the name `id` in the
current archive.
The input `id` and `genome` are not modified by this operation.
!!! note
This modification will only last for the duration of this Julia session.
If you wish to save the archive and use it for subsequent runs, use
[`set_default_archive!`](@ref) after calling this function.
"""
function add_to_current_archive!(id::AbstractString, genome::AbstractString)
if !isnumeric(first(genome))
throw(ArgumentError(lazy"""
This genome ("$genome") does not look like a genome. Are you sure you did not mix `id` and `genome`?
If you really want to associate this id with this genome, use `CrystalNets._update_archive!(id, genome)`
"""))
end
global CRYSTALNETS_ARCHIVE
for (x,y) in CRYSTALNETS_ARCHIVE
if x == genome
y == id && return
throw(ArgumentError(lazy"""
This genome is already registered under the name "$y".
If you really want to change the name associated with it, use `CrystalNets._update_archive!(id, genome)`
"""))
end
if y == id
throw(ArgumentError(lazy"""
The name $id already corresponds to a different genome: "$x"
If you really want to store another genome with the same name, use `CrystalNets._update_archive!(id, genome)`
"""))
end
end
_update_archive!(id, genome)
end
"""
make_archive(path, destination=nothing)
Make an archive from the files located in the directory given by `path` and export
it to `destination`, if specified. Each file of the directory should correspond
to a unique topology: if a topology is encountered multiple times, it will be assigned
the name of the latest file that bore it.
The archive can then be used with [`change_current_archive!(destination; validate=false)`](@ref change_current_archive!)
for instance.
"""
function make_archive(path, destination, verbose=false)
arc = Dict{String,String}()
Threads.@threads for f in readdir(path; join=false)
name = splitext(f)[1]
verbose && print("Handling "*name*"... ")
flag = false
flagerror = Ref{Any}(Tuple{Vector{Int},String}[])
genomes::Vector{Tuple{Vector{Int},String}} = try
x = topological_genome(UnderlyingNets(parse_chemfile(path*f)))
verbose && println(name*" done.")
x
catch e
flag = true
flagerror[] = e
Tuple{Vector{Int},String}[]
end
for (i, (vmap, genome)) in enumerate(genomes)
if startswith(genome, "unstable") || genome == "non-periodic"
flag = true
push!(flagerror[]::Vector{Vector{Int}}, (vmap, genome))
continue
end
arc[genome] = length(genomes) == 1 ? name : (name * '_' * string(i))
end
if flag
e = flagerror[]
isinterrupt(e) && rethrow()
if e isa Vector{Tuple{Vector{Int},String}}
for (vmap, instability) in e
println(stderr, "The component of file ", f, " containing atoms ",
vmap, " was found to be ", instability, '.')
end
else
println(stderr, "Failed for file ", f, " with error:")
showerror(stderr, e)
Base.display_error(stderr, e, catch_backtrace())
println(stderr)
end
end
end
if !(destination isa Nothing)
export_arc(destination, false, arc)
end
return arc
end