asciichem 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/asciichem/cml/translator.rb +62 -0
- data/lib/asciichem/formatter/text.rb +11 -7
- data/lib/asciichem/grammar.rb +31 -7
- data/lib/asciichem/model/atom.rb +14 -3
- data/lib/asciichem/model_adapter/from_canonical.rb +14 -3
- data/lib/asciichem/model_adapter/to_canonical.rb +14 -3
- data/lib/asciichem/transform.rb +20 -3
- data/lib/asciichem/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82e379bdd585e67154ee2ac11685d34d343fc7603dad41c8e629d27610f66a0e
|
|
4
|
+
data.tar.gz: 443e0c865b127e706b61ec9d8781b3516aecb8dfb86a83630e1363af5ae52779
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dca3abf1149dbbeb1166053cdc6afd58e90c4ad1635c5d13a8884aa11e00b1f43dbdadda6a860d5110dc3924f1ce8f01cba41992faaabbe5bd8a5b05382cdf6
|
|
7
|
+
data.tar.gz: 2fb960db615687093e7cb30f70752303d1b143fc9c3af8cd582d0fef7afcff00e45feabe88769bbb8f6af6431171b3b431d2689339a1d52903f21f397acc23a3
|
|
@@ -24,6 +24,7 @@ module AsciiChem
|
|
|
24
24
|
xml = translation.document.to_xml
|
|
25
25
|
xml = inject_atom_extensions(xml, translation.atom_mapping)
|
|
26
26
|
xml = inject_reaction_conditions(xml, formula)
|
|
27
|
+
xml = inject_metadata(xml, formula)
|
|
27
28
|
inject_molecule_extensions(xml, formula, translation)
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -32,12 +33,14 @@ module AsciiChem
|
|
|
32
33
|
top_level = Extensions.extract_top_level(xml)
|
|
33
34
|
atom_extensions = Extensions.extract(xml)
|
|
34
35
|
group_extensions = GroupExtensions.extract(xml)
|
|
36
|
+
metadata_map = extract_metadata(xml)
|
|
35
37
|
reaction_conditions = extract_reaction_conditions(xml)
|
|
36
38
|
wire_doc = Chemicalml::Cml::Document.from_xml(xml)
|
|
37
39
|
formula = AsciiChem::ModelAdapter.from_canonical(wire_doc)
|
|
38
40
|
Extensions.restore(formula, wire_doc, atom_extensions)
|
|
39
41
|
GroupExtensions.restore(formula, wire_doc, group_extensions)
|
|
40
42
|
restore_reaction_conditions(formula, reaction_conditions)
|
|
43
|
+
restore_metadata(formula, metadata_map)
|
|
41
44
|
Extensions.restore_top_level(formula, top_level)
|
|
42
45
|
formula
|
|
43
46
|
end
|
|
@@ -115,6 +118,65 @@ module AsciiChem
|
|
|
115
118
|
formula
|
|
116
119
|
end
|
|
117
120
|
|
|
121
|
+
# Inject molecule metadata via aci: attributes on <molecule>.
|
|
122
|
+
# Each {name: "k", content: "v"} produces aci:meta-k="v".
|
|
123
|
+
def inject_metadata(xml, formula)
|
|
124
|
+
require 'nokogiri'
|
|
125
|
+
doc = Nokogiri::XML(xml)
|
|
126
|
+
root = doc.root
|
|
127
|
+
molecules = formula.nodes.select { |n| n.is_a?(AsciiChem::Model::Molecule) }
|
|
128
|
+
has_meta = molecules.any? { |m| !m.metadata.empty? }
|
|
129
|
+
return xml unless has_meta
|
|
130
|
+
|
|
131
|
+
root.add_namespace(Extensions::PREFIX, Extensions::NAMESPACE) unless root.namespaces.value?(Extensions::NAMESPACE)
|
|
132
|
+
molecules.each_with_index do |mol, idx|
|
|
133
|
+
next if mol.metadata.empty?
|
|
134
|
+
|
|
135
|
+
mol_el = root.at_xpath("//cml:molecule[@id='m#{idx + 1}']", cml: Extensions::CML_NS)
|
|
136
|
+
next unless mol_el
|
|
137
|
+
|
|
138
|
+
mol.metadata.each do |m|
|
|
139
|
+
mol_el["#{Extensions::PREFIX}:meta-#{m[:name]}"] = m[:content]
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
doc.to_xml
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def extract_metadata(xml)
|
|
146
|
+
require 'nokogiri'
|
|
147
|
+
doc = Nokogiri::XML(xml)
|
|
148
|
+
result = {}
|
|
149
|
+
doc.xpath("//cml:molecule", cml: Extensions::CML_NS).each do |el|
|
|
150
|
+
id = el['id']
|
|
151
|
+
next unless id
|
|
152
|
+
|
|
153
|
+
meta = {}
|
|
154
|
+
el.attributes.each do |name, attr|
|
|
155
|
+
next unless name.start_with?('meta-')
|
|
156
|
+
next unless attr.namespace && attr.namespace.prefix == Extensions::PREFIX
|
|
157
|
+
|
|
158
|
+
key = name.sub('meta-', '')
|
|
159
|
+
meta[key] = attr.value
|
|
160
|
+
end
|
|
161
|
+
result[id] = meta unless meta.empty?
|
|
162
|
+
end
|
|
163
|
+
result
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def restore_metadata(formula, metadata_map)
|
|
167
|
+
return formula if metadata_map.empty?
|
|
168
|
+
|
|
169
|
+
formula.nodes.each_with_index do |node, idx|
|
|
170
|
+
next unless node.is_a?(AsciiChem::Model::Molecule)
|
|
171
|
+
|
|
172
|
+
meta = metadata_map["m#{idx + 1}"]
|
|
173
|
+
next unless meta
|
|
174
|
+
|
|
175
|
+
meta.each { |name, content| node.metadata << { name: name, content: content } }
|
|
176
|
+
end
|
|
177
|
+
formula
|
|
178
|
+
end
|
|
179
|
+
|
|
118
180
|
def inject_molecule_extensions(xml, formula, translation)
|
|
119
181
|
xml = inject_groups(xml, formula, translation)
|
|
120
182
|
inject_top_level(xml, formula)
|
|
@@ -57,15 +57,19 @@ module AsciiChem
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def atom_annotation(atom)
|
|
60
|
-
|
|
60
|
+
parts = []
|
|
61
61
|
if atom.x2 && atom.y2
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
coord = "@(#{format_coord(atom.x2)},#{format_coord(atom.y2)}"
|
|
63
|
+
coord << ",#{format_coord(atom.z2)}" if atom.z2
|
|
64
|
+
parts << "#{coord})"
|
|
65
|
+
end
|
|
66
|
+
parts << "@#{atom.atom_parity}" if atom.atom_parity
|
|
67
|
+
parts << "@m(#{atom.spin_multiplicity})" if atom.spin_multiplicity
|
|
68
|
+
parts << %(@t("#{atom.atom_title}")) if atom.atom_title
|
|
69
|
+
if atom.x_fract && atom.y_fract && atom.z_fract
|
|
70
|
+
parts << "@f(#{format_coord(atom.x_fract)},#{format_coord(atom.y_fract)},#{format_coord(atom.z_fract)})"
|
|
67
71
|
end
|
|
68
|
-
|
|
72
|
+
parts.join
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
def format_coord(value)
|
data/lib/asciichem/grammar.rb
CHANGED
|
@@ -182,7 +182,7 @@ module AsciiChem
|
|
|
182
182
|
atom_suffix >>
|
|
183
183
|
lewis_radicals.maybe >>
|
|
184
184
|
ring_closures.maybe.as(:ring_closures) >>
|
|
185
|
-
atom_annotation
|
|
185
|
+
atom_annotation).as(:atom)
|
|
186
186
|
end
|
|
187
187
|
|
|
188
188
|
rule(:plain_atom) do
|
|
@@ -191,15 +191,18 @@ module AsciiChem
|
|
|
191
191
|
atom_suffix >>
|
|
192
192
|
lewis_radicals.maybe >>
|
|
193
193
|
ring_closures.maybe.as(:ring_closures) >>
|
|
194
|
-
atom_annotation
|
|
194
|
+
atom_annotation).as(:atom)
|
|
195
195
|
end
|
|
196
196
|
|
|
197
|
-
# Atom annotations:
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# would require compound syntax (deferred).
|
|
197
|
+
# Atom annotations: each is independently optional via .maybe.
|
|
198
|
+
# Order matters: @(x,y) → @R/@S → @m(N) → @t("...") → @f(x,y,z).
|
|
199
|
+
# Example: C@(10,20)@R@m(2)@t("C1")@f(0.5,0.5,0.5)
|
|
201
200
|
rule(:atom_annotation) do
|
|
202
|
-
coordinate_annotation
|
|
201
|
+
coordinate_annotation.maybe >>
|
|
202
|
+
parity_annotation.maybe >>
|
|
203
|
+
multiplicity_annotation.maybe >>
|
|
204
|
+
atom_title_annotation.maybe >>
|
|
205
|
+
fractional_annotation.maybe
|
|
203
206
|
end
|
|
204
207
|
|
|
205
208
|
rule(:parity_annotation) do
|
|
@@ -214,6 +217,27 @@ module AsciiChem
|
|
|
214
217
|
str(')')
|
|
215
218
|
end
|
|
216
219
|
|
|
220
|
+
# Spin multiplicity: @m(2) for doublet, @m(1) for singlet
|
|
221
|
+
rule(:multiplicity_annotation) do
|
|
222
|
+
str('@m(') >> match('[0-9]').repeat(1).as(:spin_multiplicity) >> str(')')
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Atom title/label: @t("C1")
|
|
226
|
+
rule(:atom_title_annotation) do
|
|
227
|
+
str('@t(') >> str('"') >>
|
|
228
|
+
(str('"').absent? >> any).repeat.as(:atom_title) >>
|
|
229
|
+
str('"') >> str(')')
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Fractional coordinates (crystallographic): @f(0.5,0.25,0.75)
|
|
233
|
+
rule(:fractional_annotation) do
|
|
234
|
+
str('@f(') >>
|
|
235
|
+
float_number.as(:x_fract) >> str(',') >>
|
|
236
|
+
float_number.as(:y_fract) >> str(',') >>
|
|
237
|
+
float_number.as(:z_fract) >>
|
|
238
|
+
str(')')
|
|
239
|
+
end
|
|
240
|
+
|
|
217
241
|
rule(:float_number) do
|
|
218
242
|
str('-').maybe >> match('[0-9]').repeat(1) >> (str('.') >> match('[0-9]').repeat(0)).maybe
|
|
219
243
|
end
|
data/lib/asciichem/model/atom.rb
CHANGED
|
@@ -20,13 +20,17 @@ module AsciiChem
|
|
|
20
20
|
:charge, :oxidation_state,
|
|
21
21
|
:lone_pairs, :radical_electrons,
|
|
22
22
|
:ring_closures,
|
|
23
|
-
:x2, :y2, :z2, :atom_parity
|
|
23
|
+
:x2, :y2, :z2, :atom_parity,
|
|
24
|
+
:spin_multiplicity, :atom_title,
|
|
25
|
+
:x_fract, :y_fract, :z_fract
|
|
24
26
|
|
|
25
27
|
def initialize(element:, isotope: nil, subscript: nil,
|
|
26
28
|
superscript: nil, charge: nil, oxidation_state: nil,
|
|
27
29
|
lone_pairs: nil, radical_electrons: nil,
|
|
28
30
|
ring_closures: nil,
|
|
29
|
-
x2: nil, y2: nil, z2: nil, atom_parity: nil
|
|
31
|
+
x2: nil, y2: nil, z2: nil, atom_parity: nil,
|
|
32
|
+
spin_multiplicity: nil, atom_title: nil,
|
|
33
|
+
x_fract: nil, y_fract: nil, z_fract: nil)
|
|
30
34
|
@element = element
|
|
31
35
|
@isotope = isotope
|
|
32
36
|
@subscript = subscript
|
|
@@ -40,6 +44,11 @@ module AsciiChem
|
|
|
40
44
|
@y2 = y2
|
|
41
45
|
@z2 = z2
|
|
42
46
|
@atom_parity = atom_parity
|
|
47
|
+
@spin_multiplicity = spin_multiplicity
|
|
48
|
+
@atom_title = atom_title
|
|
49
|
+
@x_fract = x_fract
|
|
50
|
+
@y_fract = y_fract
|
|
51
|
+
@z_fract = z_fract
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
def value_attributes
|
|
@@ -48,7 +57,9 @@ module AsciiChem
|
|
|
48
57
|
oxidation_state: oxidation_state,
|
|
49
58
|
lone_pairs: lone_pairs, radical_electrons: radical_electrons,
|
|
50
59
|
ring_closures: ring_closures,
|
|
51
|
-
x2: x2, y2: y2, z2: z2, atom_parity: atom_parity
|
|
60
|
+
x2: x2, y2: y2, z2: z2, atom_parity: atom_parity,
|
|
61
|
+
spin_multiplicity: spin_multiplicity, atom_title: atom_title,
|
|
62
|
+
x_fract: x_fract, y_fract: y_fract, z_fract: z_fract }
|
|
52
63
|
end
|
|
53
64
|
|
|
54
65
|
def diagnostic_label
|
|
@@ -104,7 +104,7 @@ module AsciiChem
|
|
|
104
104
|
canonical_properties.map do |p|
|
|
105
105
|
{
|
|
106
106
|
title: p.title,
|
|
107
|
-
value: extract_scalar_value(p.
|
|
107
|
+
value: extract_scalar_value(p.scalar),
|
|
108
108
|
dict_ref: p.dict_ref,
|
|
109
109
|
convention: p.convention
|
|
110
110
|
}
|
|
@@ -113,7 +113,7 @@ module AsciiChem
|
|
|
113
113
|
|
|
114
114
|
def extract_scalar_value(value)
|
|
115
115
|
return nil if value.nil?
|
|
116
|
-
return value.
|
|
116
|
+
return value.content if value.is_a?(Chemicalml::Cml::Scalar)
|
|
117
117
|
|
|
118
118
|
value.to_s
|
|
119
119
|
end
|
|
@@ -215,7 +215,10 @@ module AsciiChem
|
|
|
215
215
|
isotope: atom.isotope,
|
|
216
216
|
subscript: subscript_from_count(atom.count),
|
|
217
217
|
charge: atom.formal_charge,
|
|
218
|
-
|
|
218
|
+
spin_multiplicity: atom.spin_multiplicity,
|
|
219
|
+
atom_title: atom.title,
|
|
220
|
+
**extract_coordinates(atom),
|
|
221
|
+
**extract_fractional(atom)
|
|
219
222
|
)
|
|
220
223
|
end
|
|
221
224
|
|
|
@@ -232,6 +235,14 @@ module AsciiChem
|
|
|
232
235
|
end
|
|
233
236
|
end
|
|
234
237
|
|
|
238
|
+
def extract_fractional(atom)
|
|
239
|
+
return {} unless atom.xFract && atom.yFract && atom.zFract
|
|
240
|
+
|
|
241
|
+
{ x_fract: atom.xFract.to_f,
|
|
242
|
+
y_fract: atom.yFract.to_f,
|
|
243
|
+
z_fract: atom.zFract.to_f }
|
|
244
|
+
end
|
|
245
|
+
|
|
235
246
|
def subscript_from_count(count)
|
|
236
247
|
return nil if count.nil? || count.to_s == "1"
|
|
237
248
|
|
|
@@ -130,7 +130,7 @@ module AsciiChem
|
|
|
130
130
|
properties.map do |p|
|
|
131
131
|
Chemicalml::Cml::Property.new(
|
|
132
132
|
title: p[:title],
|
|
133
|
-
|
|
133
|
+
scalar: build_scalar(p[:value]),
|
|
134
134
|
dict_ref: p[:dict_ref],
|
|
135
135
|
convention: p[:convention]
|
|
136
136
|
)
|
|
@@ -141,7 +141,7 @@ module AsciiChem
|
|
|
141
141
|
return nil if value.nil?
|
|
142
142
|
|
|
143
143
|
Chemicalml::Cml::Scalar.new(
|
|
144
|
-
|
|
144
|
+
content: value.to_s,
|
|
145
145
|
dict_ref: nil
|
|
146
146
|
)
|
|
147
147
|
end
|
|
@@ -330,9 +330,12 @@ module AsciiChem
|
|
|
330
330
|
formal_charge: atom.charge,
|
|
331
331
|
count: effective_count(atom, multiplier),
|
|
332
332
|
lone_pairs: atom.lone_pairs,
|
|
333
|
-
radical_electrons: atom.radical_electrons
|
|
333
|
+
radical_electrons: atom.radical_electrons,
|
|
334
|
+
spin_multiplicity: atom.spin_multiplicity,
|
|
335
|
+
title: atom.atom_title
|
|
334
336
|
}
|
|
335
337
|
merge_coordinates(attrs, atom)
|
|
338
|
+
merge_fractional_coords(attrs, atom)
|
|
336
339
|
@atoms << Chemicalml::Cml::Atom.new(**attrs)
|
|
337
340
|
emit_pending_bond(id) if @pending_bond_kind && @last_atom_id
|
|
338
341
|
@last_atom_id = id
|
|
@@ -386,6 +389,14 @@ module AsciiChem
|
|
|
386
389
|
end
|
|
387
390
|
end
|
|
388
391
|
|
|
392
|
+
def merge_fractional_coords(attrs, atom)
|
|
393
|
+
return unless atom.x_fract && atom.y_fract && atom.z_fract
|
|
394
|
+
|
|
395
|
+
attrs[:xFract] = atom.x_fract
|
|
396
|
+
attrs[:yFract] = atom.y_fract
|
|
397
|
+
attrs[:zFract] = atom.z_fract
|
|
398
|
+
end
|
|
399
|
+
|
|
389
400
|
def combine(left, right)
|
|
390
401
|
lv = integer_or_nil(left)
|
|
391
402
|
rv = integer_or_nil(right)
|
data/lib/asciichem/transform.rb
CHANGED
|
@@ -272,7 +272,12 @@ module AsciiChem
|
|
|
272
272
|
x2: float_or_nil(hash[:x2]),
|
|
273
273
|
y2: float_or_nil(hash[:y2]),
|
|
274
274
|
z2: float_or_nil(hash[:z2]),
|
|
275
|
-
atom_parity: hash[:atom_parity]&.to_s
|
|
275
|
+
atom_parity: hash[:atom_parity]&.to_s,
|
|
276
|
+
spin_multiplicity: hash[:spin_multiplicity]&.to_s,
|
|
277
|
+
atom_title: hash[:atom_title]&.to_s,
|
|
278
|
+
x_fract: float_or_nil(hash[:x_fract]),
|
|
279
|
+
y_fract: float_or_nil(hash[:y_fract]),
|
|
280
|
+
z_fract: float_or_nil(hash[:z_fract])
|
|
276
281
|
)
|
|
277
282
|
end
|
|
278
283
|
|
|
@@ -305,7 +310,9 @@ module AsciiChem
|
|
|
305
310
|
def initialize(element, isotope: nil, subscript: nil, superscript: nil,
|
|
306
311
|
lone_pairs: nil, radical_electrons: nil,
|
|
307
312
|
ring_closures: nil,
|
|
308
|
-
x2: nil, y2: nil, z2: nil, atom_parity: nil
|
|
313
|
+
x2: nil, y2: nil, z2: nil, atom_parity: nil,
|
|
314
|
+
spin_multiplicity: nil, atom_title: nil,
|
|
315
|
+
x_fract: nil, y_fract: nil, z_fract: nil)
|
|
309
316
|
@element = element
|
|
310
317
|
@isotope = isotope
|
|
311
318
|
@subscript = subscript
|
|
@@ -317,6 +324,11 @@ module AsciiChem
|
|
|
317
324
|
@y2 = y2
|
|
318
325
|
@z2 = z2
|
|
319
326
|
@atom_parity = atom_parity
|
|
327
|
+
@spin_multiplicity = spin_multiplicity
|
|
328
|
+
@atom_title = atom_title
|
|
329
|
+
@x_fract = x_fract
|
|
330
|
+
@y_fract = y_fract
|
|
331
|
+
@z_fract = z_fract
|
|
320
332
|
end
|
|
321
333
|
|
|
322
334
|
def build
|
|
@@ -335,7 +347,12 @@ module AsciiChem
|
|
|
335
347
|
x2: @x2,
|
|
336
348
|
y2: @y2,
|
|
337
349
|
z2: @z2,
|
|
338
|
-
atom_parity: @atom_parity
|
|
350
|
+
atom_parity: @atom_parity,
|
|
351
|
+
spin_multiplicity: @spin_multiplicity,
|
|
352
|
+
atom_title: @atom_title,
|
|
353
|
+
x_fract: @x_fract,
|
|
354
|
+
y_fract: @y_fract,
|
|
355
|
+
z_fract: @z_fract
|
|
339
356
|
)
|
|
340
357
|
end
|
|
341
358
|
|
data/lib/asciichem/version.rb
CHANGED