asciichem 0.4.0 → 0.5.0
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/formatter/text.rb +59 -1
- data/lib/asciichem/grammar.rb +64 -3
- data/lib/asciichem/model/calculation.rb +34 -0
- data/lib/asciichem/model/mechanism.rb +34 -0
- data/lib/asciichem/model/spectrum.rb +46 -0
- data/lib/asciichem/model/zmatrix.rb +36 -0
- data/lib/asciichem/model.rb +4 -0
- data/lib/asciichem/transform.rb +237 -4
- data/lib/asciichem/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d87f933cf6baef0a8a1f5c8e189948ba3a2f49ea9adea77b5af22854e2ccb1c
|
|
4
|
+
data.tar.gz: afb6ab79bf85415caa65fe9315a7cbc76913562cb969aa5e5947a6c7eaede38b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 912c55ba11516ed11b613ac538ee6c90d792f95b86b3337d1348e04bf50531c5f08bead8e1781155dea56b8a8cfdd66506e885ccbac713b5384acb9e982f52c2
|
|
7
|
+
data.tar.gz: c044eecacf93ae9c80a8f6ddebc66f8be38820b8157a6410d20f9bfc60428ea7377e39e8271343b4645bffa371dd67b179d3f74183c754778f0fd333984afc0a
|
|
@@ -140,7 +140,65 @@ module AsciiChem
|
|
|
140
140
|
parts.join
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
def visit_spectrum(spectrum)
|
|
144
|
+
parts = ["spectrum"]
|
|
145
|
+
parts << "[#{spectrum.type}]" if spectrum.type
|
|
146
|
+
params = spectrum.params.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
147
|
+
parts << "(#{params})" unless params.empty?
|
|
148
|
+
peak_lines = spectrum.peaks.map do |peak|
|
|
149
|
+
line = "#{peak[:position]}: #{peak[:intensity]}"
|
|
150
|
+
line += " #{peak[:multiplicity]}" if peak[:multiplicity]
|
|
151
|
+
line += %( "#{peak[:assignment]}") if peak[:assignment]
|
|
152
|
+
line
|
|
153
|
+
end
|
|
154
|
+
unless peak_lines.empty?
|
|
155
|
+
body = peak_lines.join("\n ")
|
|
156
|
+
parts << "{\n #{body}\n}"
|
|
157
|
+
end
|
|
158
|
+
parts.join
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def visit_calculation(calc)
|
|
162
|
+
parts = ["calc"]
|
|
163
|
+
params = []
|
|
164
|
+
params << calc.method if calc.method
|
|
165
|
+
params << calc.basis if calc.basis
|
|
166
|
+
parts << "(#{params.join('/')})" unless params.empty?
|
|
167
|
+
unless calc.properties.empty?
|
|
168
|
+
lines = calc.properties.map do |p|
|
|
169
|
+
line = "#{p[:title]}: #{p[:value]}"
|
|
170
|
+
line += " #{p[:units]}" if p[:units]
|
|
171
|
+
line
|
|
172
|
+
end
|
|
173
|
+
parts << "{\n #{lines.join("\n ")}\n}"
|
|
174
|
+
end
|
|
175
|
+
parts.join
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def visit_z_matrix(zm)
|
|
179
|
+
parts = ["zmatrix"]
|
|
180
|
+
unless zm.rows.empty?
|
|
181
|
+
lines = zm.rows.map do |row|
|
|
182
|
+
tokens = [row.atom]
|
|
183
|
+
tokens << row.ref1 << row.distance if row.ref1
|
|
184
|
+
tokens << row.ref2 << row.angle if row.ref2
|
|
185
|
+
tokens << row.ref3 << row.dihedral if row.ref3
|
|
186
|
+
tokens.compact.join(" ")
|
|
187
|
+
end
|
|
188
|
+
parts << "{\n #{lines.join("\n ")}\n}"
|
|
189
|
+
end
|
|
190
|
+
parts.join
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def visit_mechanism(mech)
|
|
194
|
+
parts = ["mechanism"]
|
|
195
|
+
unless mech.steps.empty? && mech.spectators.empty?
|
|
196
|
+
lines = mech.steps.map { |s| "#{s[:label]}: #{s[:reaction]}" }
|
|
197
|
+
mech.spectators.each { |sp| lines << "spectator: #{sp}" }
|
|
198
|
+
parts << "{\n #{lines.join("\n ")}\n}"
|
|
199
|
+
end
|
|
200
|
+
parts.join
|
|
201
|
+
end
|
|
144
202
|
|
|
145
203
|
def render_node(node)
|
|
146
204
|
node.accept(self)
|
data/lib/asciichem/grammar.rb
CHANGED
|
@@ -27,16 +27,16 @@ module AsciiChem
|
|
|
27
27
|
|
|
28
28
|
rule(:nodes) { node >> (spaces? >> node).repeat }
|
|
29
29
|
|
|
30
|
-
rule(:node) { reaction_cascade | reaction | electron_config | crystal | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
|
|
30
|
+
rule(:node) { reaction_cascade | reaction | electron_config | crystal | spectrum | calculation | zmatrix | mechanism | annotated_molecule | molecule | embedded_math | text_run.as(:text_run) }
|
|
31
31
|
|
|
32
32
|
# -- crystallography -------------------------------------------------
|
|
33
33
|
|
|
34
34
|
# crystal[Name](a=X,b=Y,...,sg=SG){atoms with @f(x,y,z)}
|
|
35
35
|
rule(:crystal) do
|
|
36
|
-
str('crystal') >>
|
|
36
|
+
(str('crystal') >>
|
|
37
37
|
crystal_name.maybe >>
|
|
38
38
|
crystal_params.maybe >>
|
|
39
|
-
crystal_body.maybe
|
|
39
|
+
crystal_body.maybe).as(:crystal_node)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
rule(:crystal_name) do
|
|
@@ -51,6 +51,67 @@ module AsciiChem
|
|
|
51
51
|
str('{') >> (str('}').absent? >> any).repeat.as(:crystal_body) >> str('}')
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# -- spectroscopy ---------------------------------------------------
|
|
55
|
+
|
|
56
|
+
# spectrum[type](params){peak data}
|
|
57
|
+
rule(:spectrum) do
|
|
58
|
+
(str('spectrum') >>
|
|
59
|
+
spectrum_type.maybe >>
|
|
60
|
+
spectrum_params.maybe >>
|
|
61
|
+
spectrum_body.maybe).as(:spectrum_node)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
rule(:spectrum_type) do
|
|
65
|
+
str('[') >> (str(']').absent? >> any).repeat.as(:spectrum_type) >> str(']')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
rule(:spectrum_params) do
|
|
69
|
+
str('(') >> (str(')').absent? >> any).repeat.as(:spectrum_params) >> str(')')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
rule(:spectrum_body) do
|
|
73
|
+
str('{') >> (str('}').absent? >> any).repeat.as(:spectrum_body) >> str('}')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# -- computational chemistry ----------------------------------------
|
|
77
|
+
|
|
78
|
+
# calc(method/basis){key: value units}
|
|
79
|
+
rule(:calculation) do
|
|
80
|
+
(str('calc') >>
|
|
81
|
+
calc_params.maybe >>
|
|
82
|
+
calc_body.maybe).as(:calc_node)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
rule(:calc_params) do
|
|
86
|
+
str('(') >> (str(')').absent? >> any).repeat.as(:calc_params) >> str(')')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
rule(:calc_body) do
|
|
90
|
+
str('{') >> (str('}').absent? >> any).repeat.as(:calc_body) >> str('}')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# -- Z-Matrix -------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
rule(:zmatrix) do
|
|
96
|
+
(str('zmatrix') >>
|
|
97
|
+
zmatrix_body.maybe).as(:zmatrix_node)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
rule(:zmatrix_body) do
|
|
101
|
+
str('{') >> (str('}').absent? >> any).repeat.as(:zmatrix_body) >> str('}')
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# -- reaction mechanisms --------------------------------------------
|
|
105
|
+
|
|
106
|
+
rule(:mechanism) do
|
|
107
|
+
(str('mechanism') >>
|
|
108
|
+
mechanism_body.maybe).as(:mechanism_node)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
rule(:mechanism_body) do
|
|
112
|
+
str('{') >> (str('}').absent? >> any).repeat.as(:mechanism_body) >> str('}')
|
|
113
|
+
end
|
|
114
|
+
|
|
54
115
|
# Annotated molecule: a molecule followed by one or more
|
|
55
116
|
# `@key("value")` annotations for CML metadata (names,
|
|
56
117
|
# identifiers, title, formula, labels).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciiChem
|
|
4
|
+
module Model
|
|
5
|
+
# A computational chemistry calculation result.
|
|
6
|
+
#
|
|
7
|
+
# Syntax:
|
|
8
|
+
# calc(b3lyp/6-31G*){
|
|
9
|
+
# energy: -234.5 Hartree
|
|
10
|
+
# dipole: [0.1, 0.2, 0.3] Debye
|
|
11
|
+
# }
|
|
12
|
+
class Calculation < Node
|
|
13
|
+
attr_accessor :method, :basis, :properties
|
|
14
|
+
|
|
15
|
+
def initialize(method: nil, basis: nil, properties: [])
|
|
16
|
+
@method = method
|
|
17
|
+
@basis = basis
|
|
18
|
+
@properties = properties
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value_attributes
|
|
22
|
+
{ method: method, basis: basis, properties: properties }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def children
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def diagnostic_label
|
|
30
|
+
"Calculation(#{method}/#{basis})"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciiChem
|
|
4
|
+
module Model
|
|
5
|
+
# A reaction mechanism: multi-step pathway with spectators.
|
|
6
|
+
#
|
|
7
|
+
# Syntax:
|
|
8
|
+
# mechanism{
|
|
9
|
+
# step1: A + B -> C
|
|
10
|
+
# step2: C -> D + E
|
|
11
|
+
# spectator: Na+
|
|
12
|
+
# }
|
|
13
|
+
class Mechanism < Node
|
|
14
|
+
attr_accessor :steps, :spectators
|
|
15
|
+
|
|
16
|
+
def initialize(steps: [], spectators: [])
|
|
17
|
+
@steps = steps
|
|
18
|
+
@spectators = spectators
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def value_attributes
|
|
22
|
+
{ steps: steps, spectators: spectators }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def children
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def diagnostic_label
|
|
30
|
+
"Mechanism(#{steps.length} steps)"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciiChem
|
|
4
|
+
module Model
|
|
5
|
+
# A spectroscopy result: NMR, IR, MS, UV-Vis peaks.
|
|
6
|
+
#
|
|
7
|
+
# Syntax:
|
|
8
|
+
# spectrum[nmr](type=1H,solvent=CDCl3){
|
|
9
|
+
# 1.2: 3H s "CH3"
|
|
10
|
+
# 7.2: 5H m "C6H5"
|
|
11
|
+
# }
|
|
12
|
+
#
|
|
13
|
+
# spectrum[ir]{
|
|
14
|
+
# 3300: broad "O-H stretch"
|
|
15
|
+
# }
|
|
16
|
+
#
|
|
17
|
+
# spectrum[ms]{
|
|
18
|
+
# 18: 100% "M+"
|
|
19
|
+
# }
|
|
20
|
+
class Spectrum < Node
|
|
21
|
+
attr_accessor :type, :params, :peaks
|
|
22
|
+
|
|
23
|
+
def initialize(type: nil, params: {}, peaks: [])
|
|
24
|
+
@type = type
|
|
25
|
+
@params = params
|
|
26
|
+
@peaks = peaks
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def value_attributes
|
|
30
|
+
{ type: type, params: params, peaks: peaks }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def children
|
|
34
|
+
[]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def diagnostic_label
|
|
38
|
+
"Spectrum(#{type || 'unknown'})"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_s
|
|
42
|
+
"spectrum[#{type}](#{params.map { |k, v| "#{k}=#{v}" }.join(',')})"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AsciiChem
|
|
4
|
+
module Model
|
|
5
|
+
# A Z-Matrix: internal coordinates (bond lengths, angles, torsions).
|
|
6
|
+
#
|
|
7
|
+
# Syntax:
|
|
8
|
+
# zmatrix{
|
|
9
|
+
# C1
|
|
10
|
+
# H2 C1 1.09
|
|
11
|
+
# H3 C1 1.09 H2 109.5
|
|
12
|
+
# H4 C1 1.09 H2 109.5 H3 120.0
|
|
13
|
+
# }
|
|
14
|
+
class ZMatrix < Node
|
|
15
|
+
ZRow = Struct.new(:atom, :ref1, :distance, :ref2, :angle, :ref3, :dihedral, keyword_init: true)
|
|
16
|
+
|
|
17
|
+
attr_accessor :rows
|
|
18
|
+
|
|
19
|
+
def initialize(rows: [])
|
|
20
|
+
@rows = rows
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def value_attributes
|
|
24
|
+
{ rows: rows }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def children
|
|
28
|
+
[]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def diagnostic_label
|
|
32
|
+
"ZMatrix(#{rows.length} rows)"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/asciichem/model.rb
CHANGED
|
@@ -8,17 +8,21 @@ module AsciiChem
|
|
|
8
8
|
module Model
|
|
9
9
|
autoload :Atom, "asciichem/model/atom"
|
|
10
10
|
autoload :Bond, "asciichem/model/bond"
|
|
11
|
+
autoload :Calculation, "asciichem/model/calculation"
|
|
11
12
|
autoload :Crystal, "asciichem/model/crystal"
|
|
12
13
|
autoload :ElectronConfiguration, "asciichem/model/electron_configuration"
|
|
13
14
|
autoload :EmbeddedMath, "asciichem/model/embedded_math"
|
|
14
15
|
autoload :Formula, "asciichem/model/formula"
|
|
15
16
|
autoload :Group, "asciichem/model/group"
|
|
16
17
|
autoload :Identifier, "asciichem/model/identifier"
|
|
18
|
+
autoload :Mechanism, "asciichem/model/mechanism"
|
|
17
19
|
autoload :Molecule, "asciichem/model/molecule"
|
|
18
20
|
autoload :Name, "asciichem/model/name"
|
|
19
21
|
autoload :Node, "asciichem/model/node"
|
|
20
22
|
autoload :Reaction, "asciichem/model/reaction"
|
|
21
23
|
autoload :ReactionCascade, "asciichem/model/reaction_cascade"
|
|
24
|
+
autoload :Spectrum, "asciichem/model/spectrum"
|
|
22
25
|
autoload :Text, "asciichem/model/text"
|
|
26
|
+
autoload :ZMatrix, "asciichem/model/zmatrix"
|
|
23
27
|
end
|
|
24
28
|
end
|
data/lib/asciichem/transform.rb
CHANGED
|
@@ -151,10 +151,45 @@ module AsciiChem
|
|
|
151
151
|
# Grammar captures crystal_name, crystal_params, and crystal_body
|
|
152
152
|
# as optional strings. CrystalBuilder parses them into the model.
|
|
153
153
|
|
|
154
|
-
rule(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
rule(crystal_node: subtree(:data)) do
|
|
155
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
156
|
+
CrystalBuilder.new(
|
|
157
|
+
hash[:crystal_name],
|
|
158
|
+
hash[:crystal_params],
|
|
159
|
+
hash[:crystal_body]
|
|
160
|
+
).build
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# -- spectra --------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
rule(spectrum_node: subtree(:data)) do
|
|
166
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
167
|
+
SpectrumBuilder.new(
|
|
168
|
+
hash[:spectrum_type],
|
|
169
|
+
hash[:spectrum_params],
|
|
170
|
+
hash[:spectrum_body]
|
|
171
|
+
).build
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# -- computational chemistry ----------------------------------------
|
|
175
|
+
|
|
176
|
+
rule(calc_node: subtree(:data)) do
|
|
177
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
178
|
+
CalculationBuilder.new(hash[:calc_params], hash[:calc_body]).build
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# -- Z-Matrix -------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
rule(zmatrix_node: subtree(:data)) do
|
|
184
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
185
|
+
ZMatrixBuilder.new(hash[:zmatrix_body]).build
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# -- reaction mechanisms --------------------------------------------
|
|
189
|
+
|
|
190
|
+
rule(mechanism_node: subtree(:data)) do
|
|
191
|
+
hash = data.is_a?(Hash) ? data : {}
|
|
192
|
+
MechanismBuilder.new(hash[:mechanism_body]).build
|
|
158
193
|
end
|
|
159
194
|
|
|
160
195
|
# -- internal helpers ------------------------------------------------
|
|
@@ -215,6 +250,204 @@ module AsciiChem
|
|
|
215
250
|
end
|
|
216
251
|
end
|
|
217
252
|
|
|
253
|
+
# Builds a Spectrum from parsed grammar captures. Parses peak
|
|
254
|
+
# lines from the body string.
|
|
255
|
+
class SpectrumBuilder
|
|
256
|
+
def initialize(type_str, params_str, body_str)
|
|
257
|
+
@type = strip_value(type_str)
|
|
258
|
+
@params_str = strip_value(params_str)
|
|
259
|
+
@body_str = strip_value(body_str)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def build
|
|
263
|
+
Model::Spectrum.new(
|
|
264
|
+
type: @type,
|
|
265
|
+
params: parse_params(@params_str),
|
|
266
|
+
peaks: parse_peaks(@body_str)
|
|
267
|
+
)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
private
|
|
271
|
+
|
|
272
|
+
def strip_value(value)
|
|
273
|
+
return nil if value.nil?
|
|
274
|
+
|
|
275
|
+
s = value.to_s.strip
|
|
276
|
+
s.empty? ? nil : s
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def parse_params(str)
|
|
280
|
+
return {} unless str
|
|
281
|
+
|
|
282
|
+
str.split(',').each_with_object({}) do |pair, memo|
|
|
283
|
+
key, val = pair.strip.split('=', 2)
|
|
284
|
+
memo[key] = val&.strip if key
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def parse_peaks(str)
|
|
289
|
+
return [] unless str
|
|
290
|
+
|
|
291
|
+
str.split("\n").filter_map { |line| parse_peak(line.strip) }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def parse_peak(line)
|
|
295
|
+
return nil if line.empty?
|
|
296
|
+
|
|
297
|
+
assignment = nil
|
|
298
|
+
match = line.match(/"([^"]*)"/)
|
|
299
|
+
if match
|
|
300
|
+
assignment = match[1]
|
|
301
|
+
line = line.sub(/"[^"]*"/, '').strip
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
pos, rest = line.split(':', 2)
|
|
305
|
+
tokens = rest&.strip&.split(/\s+/) || []
|
|
306
|
+
|
|
307
|
+
{
|
|
308
|
+
position: pos&.strip,
|
|
309
|
+
intensity: tokens[0],
|
|
310
|
+
multiplicity: tokens[1],
|
|
311
|
+
assignment: assignment
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Builds a Calculation from grammar captures.
|
|
317
|
+
# Params: "method/basis" string. Body: key-value lines.
|
|
318
|
+
class CalculationBuilder
|
|
319
|
+
def initialize(params_str, body_str)
|
|
320
|
+
@params_str = strip_value(params_str)
|
|
321
|
+
@body_str = strip_value(body_str)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def build
|
|
325
|
+
method, basis = parse_method_basis(@params_str)
|
|
326
|
+
Model::Calculation.new(
|
|
327
|
+
method: method,
|
|
328
|
+
basis: basis,
|
|
329
|
+
properties: parse_properties(@body_str)
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
private
|
|
334
|
+
|
|
335
|
+
def strip_value(value)
|
|
336
|
+
return nil if value.nil?
|
|
337
|
+
|
|
338
|
+
s = value.to_s.strip
|
|
339
|
+
s.empty? ? nil : s
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def parse_method_basis(str)
|
|
343
|
+
return [nil, nil] unless str
|
|
344
|
+
|
|
345
|
+
parts = str.split('/', 2)
|
|
346
|
+
[parts[0]&.strip, parts[1]&.strip]
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def parse_properties(str)
|
|
350
|
+
return [] unless str
|
|
351
|
+
|
|
352
|
+
str.split("\n").filter_map do |line|
|
|
353
|
+
line = line.strip
|
|
354
|
+
next nil if line.empty?
|
|
355
|
+
|
|
356
|
+
key, rest = line.split(':', 2)
|
|
357
|
+
next nil unless key
|
|
358
|
+
|
|
359
|
+
tokens = rest&.strip&.split(/\s+/) || []
|
|
360
|
+
{ title: key.strip, value: tokens[0], units: tokens[1] }
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Builds a ZMatrix from grammar captures.
|
|
366
|
+
# Each body line: atom [ref1 distance] [ref2 angle] [ref3 dihedral]
|
|
367
|
+
class ZMatrixBuilder
|
|
368
|
+
def initialize(body_str)
|
|
369
|
+
@body_str = strip_value(body_str)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def build
|
|
373
|
+
Model::ZMatrix.new(rows: parse_rows(@body_str))
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
private
|
|
377
|
+
|
|
378
|
+
def strip_value(value)
|
|
379
|
+
return nil if value.nil?
|
|
380
|
+
|
|
381
|
+
s = value.to_s.strip
|
|
382
|
+
s.empty? ? nil : s
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def parse_rows(str)
|
|
386
|
+
return [] unless str
|
|
387
|
+
|
|
388
|
+
str.split("\n").filter_map { |line| parse_row(line.strip) }
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def parse_row(line)
|
|
392
|
+
return nil if line.empty?
|
|
393
|
+
|
|
394
|
+
tokens = line.split(/\s+/)
|
|
395
|
+
Model::ZMatrix::ZRow.new(
|
|
396
|
+
atom: tokens[0],
|
|
397
|
+
ref1: tokens[1],
|
|
398
|
+
distance: tokens[2],
|
|
399
|
+
ref2: tokens[3],
|
|
400
|
+
angle: tokens[4],
|
|
401
|
+
ref3: tokens[5],
|
|
402
|
+
dihedral: tokens[6]
|
|
403
|
+
)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Builds a Mechanism from grammar captures.
|
|
408
|
+
# Each body line: key: value (step1: reaction, spectator: ion)
|
|
409
|
+
class MechanismBuilder
|
|
410
|
+
def initialize(body_str)
|
|
411
|
+
@body_str = strip_value(body_str)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def build
|
|
415
|
+
steps = []
|
|
416
|
+
spectators = []
|
|
417
|
+
parse_entries(@body_str).each do |key, value|
|
|
418
|
+
if key == 'spectator'
|
|
419
|
+
spectators.concat(value.split(/\s+/).map(&:strip))
|
|
420
|
+
else
|
|
421
|
+
steps << { label: key, reaction: value }
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
Model::Mechanism.new(steps: steps, spectators: spectators)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
private
|
|
428
|
+
|
|
429
|
+
def strip_value(value)
|
|
430
|
+
return nil if value.nil?
|
|
431
|
+
|
|
432
|
+
s = value.to_s.strip
|
|
433
|
+
s.empty? ? nil : s
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def parse_entries(str)
|
|
437
|
+
return [] unless str
|
|
438
|
+
|
|
439
|
+
str.split("\n").filter_map do |line|
|
|
440
|
+
line = line.strip
|
|
441
|
+
next nil if line.empty?
|
|
442
|
+
|
|
443
|
+
key, val = line.split(':', 2)
|
|
444
|
+
next nil unless key && val
|
|
445
|
+
|
|
446
|
+
[key.strip, val.strip]
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
218
451
|
# Strips the surrounding `"..."` quotes from a quoted text match.
|
|
219
452
|
# Used by both `text_run` and `group_text_run` rules so the
|
|
220
453
|
# model never carries the delimiters — the formatter re-adds them
|
data/lib/asciichem/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asciichem
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -155,18 +155,22 @@ files:
|
|
|
155
155
|
- lib/asciichem/model.rb
|
|
156
156
|
- lib/asciichem/model/atom.rb
|
|
157
157
|
- lib/asciichem/model/bond.rb
|
|
158
|
+
- lib/asciichem/model/calculation.rb
|
|
158
159
|
- lib/asciichem/model/crystal.rb
|
|
159
160
|
- lib/asciichem/model/electron_configuration.rb
|
|
160
161
|
- lib/asciichem/model/embedded_math.rb
|
|
161
162
|
- lib/asciichem/model/formula.rb
|
|
162
163
|
- lib/asciichem/model/group.rb
|
|
163
164
|
- lib/asciichem/model/identifier.rb
|
|
165
|
+
- lib/asciichem/model/mechanism.rb
|
|
164
166
|
- lib/asciichem/model/molecule.rb
|
|
165
167
|
- lib/asciichem/model/name.rb
|
|
166
168
|
- lib/asciichem/model/node.rb
|
|
167
169
|
- lib/asciichem/model/reaction.rb
|
|
168
170
|
- lib/asciichem/model/reaction_cascade.rb
|
|
171
|
+
- lib/asciichem/model/spectrum.rb
|
|
169
172
|
- lib/asciichem/model/text.rb
|
|
173
|
+
- lib/asciichem/model/zmatrix.rb
|
|
170
174
|
- lib/asciichem/model_adapter.rb
|
|
171
175
|
- lib/asciichem/model_adapter/from_canonical.rb
|
|
172
176
|
- lib/asciichem/model_adapter/to_canonical.rb
|