From 97647cd655a5693fe98b029ad047f34deca4639e Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 13:20:17 -0700 Subject: [PATCH 001/105] restructuring framework reader to accept non-P1 cifs. atom_site information moved, working on reading symmetry --- src/Crystal.jl | 139 +++++++++++++++++++++++++++++++------------------ 1 file changed, 89 insertions(+), 50 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 7d01dae68..f4512e95d 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -54,14 +54,19 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, yf = Array{Float64, 1}() zf = Array{Float64, 1}() coords = Array{Float64, 2}(undef, 3, 0) + symmetry_rules = Array{Function, 2}(undef, 3, 0) # Start of .cif reader if extension == "cif" data = Dict{AbstractString, Float64}() loop_starts = -1 - for (i, line) in enumerate(lines) - line = split(line) + i = 1 + p1_symmetry = false + symmetry_info = false + atom_info = false + while i <= length(lines) + line = split(lines[i]) # Skip empty lines if length(line) == 0 continue @@ -70,18 +75,89 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Make sure the space group is P1 if line[1] == "_symmetry_space_group_name_H-M" if length(line) == 3 - @assert (occursin("P1", line[2] * line[3]) || + p1_symmetry = (occursin("P1", line[2] * line[3]) || occursin("P 1", line[2] * line[3]) || occursin("-P1", line[2] * line[3])) - "cif must have P1 symmetry.\n" elseif length(line) == 2 - @assert (occursin("P1", line[2]) || + p1_symmetry = (occursin("P1", line[2]) || occursin("P 1", line[2]) || occursin("-P1", line[2])) - "cif must have P1 symmetry.\n" - else - println(line) - error("Does this .cif have P1 symmetry? Use `convert_cif_to_P1_symmetry` to convert to P1 symmetry") + end + end + + # checking for information about atom sites and symmetry + if line[1] == "loop_" + next_line = split(lines[i+1]) + if occursin("_atom_site", next_line[1]) + atom_info = true + atom_column_name = "" + # name_to_column is a dictionary that e.g. returns which column contains x fractional coord + # use example: name_to_column["_atom_site_fract_x"] gives 3 + name_to_column = Dict{AbstractString, Int}() + + i += 1 + loop_starts = i + while length(split(lines[i])) == 1 + if i == loop_starts + atom_column_name = split(lines[i])[1] + end + name_to_column[split(lines[i])[1]] = i + 1 - loop_starts + # iterate to next line in file + i += 1 + end + + # read in atom_site info and store it in column based on + # the name_to_column dictionary + while length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) + + push!(species, Symbol(line[name_to_column[atom_column_name]])) + coords = [coords [mod(parse(Float64, line[name_to_column["_atom_site_fract_x"]]), 1.0), + mod(parse(Float64, line[name_to_column["_atom_site_fract_y"]]), 1.0), + mod(parse(Float64, line[name_to_column["_atom_site_fract_z"]]), 1.0)]] + # If charges present, import them + if haskey(name_to_column, "_atom_site_charge") + push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) + else + push!(charge_values, 0.0) + end + # iterate to next line in file + i += 1 + end + + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue + # only read in symmetry if the structure is not in P1 symmetry + elseif occursin("_symmetry_equiv_pos", next_line[1]) && !p1_symmetry + symmetry_info = true + symmetry_column_name = "" + # name_to_column is a dictionary that e.g. returns which column contains xyz remapping + # use example: name_to_column["_symmetry_equiv_pos_as_xyz"] gives 2 + name_to_column = Dict{AbstractString, Int}() + + i += 1 + while length(split(lines[i])) == 1 + name_to_column[split(lines[i])[1]] = i + 1 - loop_starts + # iterate to next line in file + i += 1 + end + + @assert haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") "Need column name `_symmetry_equiv_pos_xyz` to parse symmetry information" + + symmetry_count = 1 + while length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) + sym_funcs = split(line["_symmetry_equiv_pos_as_xyz"], ",") + + i += 1 + symmetry_count += 1 + end + + # finish reading in symmetry information, skip to next + # iteration of outer while-loop + continue end end @@ -98,52 +174,15 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, data[angle] = parse(Float64, split(line[2],'(')[1]) * pi / 180.0 end end - - # As soon as we reach the coordinate loop, we break this for-loop - # and replace it with a while-loop further down - if line[1] == "loop_" - next_line = split(lines[i+1]) - if occursin("_atom_site", next_line[1]) - loop_starts = i + 1 - break - end - end end # End loop over lines - if loop_starts == -1 + if !atom_info error("Could not find _atom_site* after loop_ in .cif file\n") end - atom_column_name = "" - # name_to_column is a dictionary that e.g. returns which column contains x fractional coord - # use example: name_to_column["_atom_site_fract_x"] gives 3 - name_to_column = Dict{AbstractString, Int}() - - i = loop_starts - while length(split(lines[i])) == 1 - if i == loop_starts - atom_column_name = split(lines[i])[1] - end - name_to_column[split(lines[i])[1]] = i + 1 - loop_starts - i += 1 - end - - for i = loop_starts+length(name_to_column):length(lines) - line = split(lines[i]) - if length(line) != length(name_to_column) - break - end - - push!(species, Symbol(line[name_to_column[atom_column_name]])) - coords = [coords [mod(parse(Float64, line[name_to_column["_atom_site_fract_x"]]), 1.0), - mod(parse(Float64, line[name_to_column["_atom_site_fract_y"]]), 1.0), - mod(parse(Float64, line[name_to_column["_atom_site_fract_z"]]), 1.0)]] - # If charges present, import them - if haskey(name_to_column, "_atom_site_charge") - push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) - else - push!(charge_values, 0.0) - end + # Structure must either be in P1 symmetry or have replication information + if !p1_symmetry && !symmetry_info + error("If structure is not in P1 symmetry it must have replication information") end a = data["a"] From 7f7f5ec1fa4f92fdef6c0aecf39297506b98742f Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 13:37:38 -0700 Subject: [PATCH 002/105] fix issue with not moving forward in file --- src/Crystal.jl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index f4512e95d..ef1988915 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -69,6 +69,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, line = split(lines[i]) # Skip empty lines if length(line) == 0 + i += 1 continue end @@ -108,7 +109,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # read in atom_site info and store it in column based on # the name_to_column dictionary - while length(split(lines[i])) == length(name_to_column) + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) push!(species, Symbol(line[name_to_column[atom_column_name]])) @@ -147,7 +148,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, @assert haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") "Need column name `_symmetry_equiv_pos_xyz` to parse symmetry information" symmetry_count = 1 - while length(split(lines[i])) == length(name_to_column) + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) sym_funcs = split(line["_symmetry_equiv_pos_as_xyz"], ",") @@ -174,6 +175,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, data[angle] = parse(Float64, split(line[2],'(')[1]) * pi / 180.0 end end + + i += 1 end # End loop over lines if !atom_info From 530a92718bba82156cc4d474ced23af2a43d94bb Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 16:02:21 -0700 Subject: [PATCH 003/105] PorousMaterials can read in any symmetry to P1 (as longs as replication rules are given in the cif) --- src/Crystal.jl | 57 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index ef1988915..d11a204b3 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -59,6 +59,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Start of .cif reader if extension == "cif" + coords_simple = Array{Float64, 2}(undef, 3, 0) + charges_simple = Array{Float64, 1}() + species_simple = Array{Symbol, 1}() data = Dict{AbstractString, Float64}() loop_starts = -1 i = 1 @@ -112,15 +115,15 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) - push!(species, Symbol(line[name_to_column[atom_column_name]])) - coords = [coords [mod(parse(Float64, line[name_to_column["_atom_site_fract_x"]]), 1.0), - mod(parse(Float64, line[name_to_column["_atom_site_fract_y"]]), 1.0), - mod(parse(Float64, line[name_to_column["_atom_site_fract_z"]]), 1.0)]] + push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) + coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] # If charges present, import them if haskey(name_to_column, "_atom_site_charge") - push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) + push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) else - push!(charge_values, 0.0) + push!(charges_simple, 0.0) end # iterate to next line in file i += 1 @@ -139,6 +142,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, name_to_column = Dict{AbstractString, Int}() i += 1 + loop_starts = i while length(split(lines[i])) == 1 name_to_column[split(lines[i])[1]] = i + 1 - loop_starts # iterate to next line in file @@ -147,14 +151,35 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, @assert haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") "Need column name `_symmetry_equiv_pos_xyz` to parse symmetry information" - symmetry_count = 1 - while i <= length(lines) && length(split(lines[i])) == length(name_to_column) + symmetry_count = 0 + # CSD stores symmetry as one column in a string that ends + # up getting split on the spaces between commas (i.e. its + # not really one column) the length(name_to_column) + 2 + # should catch this hopefully there aren't other weird + # ways of writing cifs... + while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' + symmetry_count += 1 line = split(lines[i]) - sym_funcs = split(line["_symmetry_equiv_pos_as_xyz"], ",") + sym_funcs = split(line[name_to_column["_symmetry_equiv_pos_as_xyz"]], [' ', ',', '''], keepempty=false) + + new_sym_rule = Array{Function, 1}(undef, 3) + + for sym in sym_funcs + if occursin("x", sym) + new_sym_rule[1] = eval(Meta.parse("x -> " * sym)) + elseif occursin("y", sym) + new_sym_rule[2] = eval(Meta.parse("y -> " * sym)) + elseif occursin("z", sym) + new_sym_rule[3] = eval(Meta.parse("z -> " * sym)) + end + end + + symmetry_rules = [symmetry_rules new_sym_rule] i += 1 - symmetry_count += 1 end + + @assert symmetry_count == size(symmetry_rules, 2) "number of symmetry rules must match the count" # finish reading in symmetry information, skip to next # iteration of outer while-loop @@ -195,6 +220,18 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, β = data["beta"] γ = data["gamma"] + if !p1_symmetry && symmetry_info + for i in 1:size(symmetry_rules, 2) + # check for making sure the mapping of function was working correctly + #@printf("i: %d\tproof of mapping: %0.3f %0.3f %0.3f\n", i, Base.invokelatest.(symmetry_rules[:, i], [0.5, 0.5, 0.5])...) + coords = [coords Base.invokelatest.(symmetry_rules[:, i], coords_simple)] + charge_values = [charge_values; charges_simple] + species = [species; species_simple] + end + elseif p1_symmetry + coords = deepcopy(coords_simple) + end + # Start of cssr reader #TODO make sure this works for different .cssr files! elseif extension == "cssr" # First line contains unit cell lenghts From 9a1673c291fbda8f68b88db6afe9d011397275d1 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 16:23:33 -0700 Subject: [PATCH 004/105] fixed issue with P1 symmetry not getting full charges and species --- src/Crystal.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index d11a204b3..64511c4a2 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -230,6 +230,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end elseif p1_symmetry coords = deepcopy(coords_simple) + charge_values = deepcopy(charges_simple) + species = deepcopy(species_simple) end # Start of cssr reader #TODO make sure this works for different .cssr files! From 4dd91a7e768f21c25d2266639b5c127c6f424e5a Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 17:02:00 -0700 Subject: [PATCH 005/105] add warning for converting to P1, fix some issues with reading files from openbabel P1 conversions --- src/Crystal.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 64511c4a2..cfff93bfe 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -77,7 +77,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end # Make sure the space group is P1 - if line[1] == "_symmetry_space_group_name_H-M" + if line[1] == "_symmetry_space_group_name_H-M" || line[1] == "_space_group_name_H-M_alt" if length(line) == 3 p1_symmetry = (occursin("P1", line[2] * line[3]) || occursin("P 1", line[2] * line[3]) || @@ -91,7 +91,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # checking for information about atom sites and symmetry if line[1] == "loop_" - next_line = split(lines[i+1]) + next_line = split(lines[i+1], [' ', '\t']; keepempty=false) if occursin("_atom_site", next_line[1]) atom_info = true atom_column_name = "" @@ -157,10 +157,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # not really one column) the length(name_to_column) + 2 # should catch this hopefully there aren't other weird # ways of writing cifs... - while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' + while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' && !occursin("loop_", lines[i]) + @printf("%s\n", lines[i]) symmetry_count += 1 - line = split(lines[i]) - sym_funcs = split(line[name_to_column["_symmetry_equiv_pos_as_xyz"]], [' ', ',', '''], keepempty=false) + line = lines[i] + sym_funcs = split(line, [' ', ',', '''], keepempty=false) new_sym_rule = Array{Function, 1}(undef, 3) @@ -221,6 +222,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, γ = data["gamma"] if !p1_symmetry && symmetry_info + @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 to be ready for simulation use.", filename) for i in 1:size(symmetry_rules, 2) # check for making sure the mapping of function was working correctly #@printf("i: %d\tproof of mapping: %0.3f %0.3f %0.3f\n", i, Base.invokelatest.(symmetry_rules[:, i], [0.5, 0.5, 0.5])...) @@ -228,6 +230,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, charge_values = [charge_values; charges_simple] species = [species; species_simple] end + coords .= mod.(coords, 1.0) elseif p1_symmetry coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) From d49efa56ae5df95773db3047df4e7c4e2ae55b85 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 19 Jul 2019 17:33:47 -0700 Subject: [PATCH 006/105] working on verifying that the P1 structure we generate is the same as the P1 structure from oBabel. Change isapprox in Atoms and Charges to be based on Sets instead of Arrays --- src/Crystal.jl | 5 ++++- src/Matter.jl | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index cfff93bfe..88b6ddc33 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -808,7 +808,7 @@ function Base.show(io::IO, framework::Framework) println(io, "Chemical formula: ", chemical_formula(framework)) end -function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) +function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false, verbose::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) return false @@ -822,5 +822,8 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) end charges_flag = isapprox(f1.charges, f2.charges) atoms_flag = isapprox(f1.atoms, f2.atoms) + if verbose + @printf("Box flag:%s\nAtoms flag:%s\nCharges flag%s\n", box_flag ? "TRUE" : "FALSE", atoms_flag ? "TRUE" : "FALSE", charges_flag ? "TRUE" : "FALSE") + end return box_flag && charges_flag && atoms_flag end diff --git a/src/Matter.jl b/src/Matter.jl index e54e06513..aebaf708f 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -20,7 +20,8 @@ end # compute n_species automatically from array sizes Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) -Base.isapprox(a1::Atoms, a2::Atoms) = (a1.species == a2.species) && isapprox(a1.xf, a2.xf) +Base.isapprox(a1::Atoms, a2::Atoms) = issetequal(Set(a1.species), Set(a2.species)) && + issetequal(Set([a1.xf[:, i] for i in 1:a1.n_atoms]), Set([a2.xf[:, i] for i in 1:a2.n_atoms])) """ Data structure holds a set of point charges and their positions in fractional coordinates. @@ -44,4 +45,5 @@ end # compute n_charges automatically from array sizes Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) -Base.isapprox(c1::Charges, c2::Charges) = (isapprox(c1.q, c2.q) && isapprox(c1.xf, c2.xf)) +Base.isapprox(c1::Charges, c2::Charges) = issetequal(Set(c1.q), Set(c2.q)) && + issetequal(Set([c1.xf[:, i] for i in 1:c1.n_charges]), Set([c2.xf[:, i] for i in 1:c2.n_charges])) From c27a63624ed5024e46043635e6f39c5d9720e1f3 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 24 Jul 2019 11:30:53 -0700 Subject: [PATCH 007/105] add unit test for comparing our P1 converter to the openBabel converter --- src/Crystal.jl | 3 +- test/crystal_test.jl | 12 +++ test/data/crystals/KAXQIL_clean.cif | 72 +++++++++++++ test/data/crystals/KAXQIL_clean_P1.cif | 141 +++++++++++++++++++++++++ 4 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 test/data/crystals/KAXQIL_clean.cif create mode 100644 test/data/crystals/KAXQIL_clean_P1.cif diff --git a/src/Crystal.jl b/src/Crystal.jl index 88b6ddc33..5a13a2a8c 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -158,7 +158,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # should catch this hopefully there aren't other weird # ways of writing cifs... while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' && !occursin("loop_", lines[i]) - @printf("%s\n", lines[i]) symmetry_count += 1 line = lines[i] sym_funcs = split(line, [' ', ',', '''], keepempty=false) @@ -823,7 +822,7 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false, ver charges_flag = isapprox(f1.charges, f2.charges) atoms_flag = isapprox(f1.atoms, f2.atoms) if verbose - @printf("Box flag:%s\nAtoms flag:%s\nCharges flag%s\n", box_flag ? "TRUE" : "FALSE", atoms_flag ? "TRUE" : "FALSE", charges_flag ? "TRUE" : "FALSE") + @printf("Box flag: %s\nAtoms flag: %s\nCharges flag: %s\n", box_flag ? "TRUE" : "FALSE", atoms_flag ? "TRUE" : "FALSE", charges_flag ? "TRUE" : "FALSE") end return box_flag && charges_flag && atoms_flag end diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 88a07619c..539119e19 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -33,6 +33,18 @@ using Random framework_rewritten = Framework("rewritten_test_structure2.cif") @test isapprox(framework, framework_rewritten) + # test .cif reader for non-P1 symmetry + # no atoms should overlap + # should palce atoms in the same positions as the P1 conversion using + # openBabel + non_P1_framework = Framework("KAXQIL_clean.cif") + P1_framework = Framework("KAXQIL_clean_P1.cif") + # need to round to five digits because the equality fails when they have + # different significant digits + non_P1_framework.atoms.xf .= round.(non_P1_framework.atoms.xf, digits=5) + P1_framework.atoms.xf .= round.(P1_framework.atoms.xf, digits=5) + @test isapprox(non_P1_framework, P1_framework) + # test .cssr reader too; test_structure2.{cif,cssr} designed to be the same. framework_from_cssr = Framework("test_structure2.cssr") strip_numbers_from_atom_labels!(framework_from_cssr) diff --git a/test/data/crystals/KAXQIL_clean.cif b/test/data/crystals/KAXQIL_clean.cif new file mode 100644 index 000000000..e539e59fa --- /dev/null +++ b/test/data/crystals/KAXQIL_clean.cif @@ -0,0 +1,72 @@ + +####################################################################### +# +# Cambridge Crystallographic Data Centre +# CCDC +# +####################################################################### +# +# If this CIF has been generated from an entry in the Cambridge +# Structural Database, then it will include bibliographic, chemical, +# crystal, experimental, refinement or atomic coordinate data resulting +# from the CCDC's data processing and validation procedures. +# +####################################################################### + +data_casdb +_symmetry_cell_setting monoclinic +_symmetry_space_group_name_H-M 'P 21/n' +_symmetry_Int_Tables_number 14 +_space_group_name_Hall '-P 2yn' +loop_ +_symmetry_equiv_pos_site_id +_symmetry_equiv_pos_as_xyz +1 x,y,z +2 1/2-x,1/2+y,1/2-z +3 -x,-y,-z +4 1/2+x,1/2-y,1/2+z +_cell_length_a 11.8214(3) +_cell_length_b 5.56730(13) +_cell_length_c 22.7603(5) +_cell_angle_alpha 90.00 +_cell_angle_beta 100.356(2) +_cell_angle_gamma 90.00 +_cell_volume 1473.53 +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +Ca1 Ca 0.36318(3) 0.24415(5) 0.277126(14) +S1 S 0.60165(3) 0.03637(7) 0.392320(16) +O6 O 0.50690(10) 0.1950(2) 0.36904(5) +O1 O 0.73774(11) 0.0848(2) 0.69064(5) +O5 O 0.58466(12) -0.2189(2) 0.38824(6) +O3 O 1.01577(12) 0.4743(2) 0.25622(6) +O4 O 1.04305(12) 0.0809(2) 0.24715(7) +C4 C 0.99546(14) 0.2586(3) 0.26511(8) +C4A C 0.72326(14) 0.2607(3) 0.65507(7) +C6 C 0.64494(14) 0.1092(3) 0.46864(7) +C7 C 0.71894(14) 0.1104(3) 0.35781(7) +C8 C 0.80704(16) -0.0567(3) 0.36004(8) +H8 H 0.8055 -0.1996 0.3811 +C9 C 0.72975(17) -0.0114(3) 0.56772(8) +H9 H 0.7682 -0.1249 0.5939 +C10 C 0.69872(14) 0.2073(3) 0.58931(7) +C11 C 0.61656(16) 0.3313(3) 0.48920(8) +H11 H 0.5794 0.4459 0.4628 +C12 C 0.72051(15) 0.3272(3) 0.32810(8) +H12 H 0.6612 0.4377 0.3271 +C13 C 0.81276(15) 0.3756(3) 0.29975(8) +H13 H 0.8161 0.5217 0.2803 +C14 C 0.89738(16) -0.0071(3) 0.33040(9) +H14 H 0.9561 -0.1186 0.3309 +C15 C 0.70345(18) -0.0610(3) 0.50695(8) +H15 H 0.7249 -0.2066 0.4922 +C16 C 0.90018(14) 0.2083(3) 0.30006(7) +C17 C 0.64454(16) 0.3796(3) 0.54979(8) +H17 H 0.6269 0.5287 0.5642 +O2 O 0.73242(11) 0.4749(2) 0.67168(6) + +#END diff --git a/test/data/crystals/KAXQIL_clean_P1.cif b/test/data/crystals/KAXQIL_clean_P1.cif new file mode 100644 index 000000000..dbd81888a --- /dev/null +++ b/test/data/crystals/KAXQIL_clean_P1.cif @@ -0,0 +1,141 @@ +# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net +data_I +_chemical_name_common 'casdb' +_cell_length_a 11.8214 +_cell_length_b 5.5673 +_cell_length_c 22.7603 +_cell_angle_alpha 90 +_cell_angle_beta 100.356 +_cell_angle_gamma 90 +_space_group_name_H-M_alt 'P 1' +_space_group_name_Hall 'P 1' +loop_ + _symmetry_equiv_pos_as_xyz + x,y,z +loop_ + _atom_site_label + _atom_site_type_symbol + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + Ca1 Ca 0.36318 0.24415 0.27713 1.000 + S1 S 0.60165 0.03637 0.39232 1.000 + O6 O 0.50690 0.19500 0.36904 1.000 + O1 O 0.73774 0.08480 0.69064 1.000 + O5 O 0.58466 0.78110 0.38824 1.000 + O3 O 0.01577 0.47430 0.25622 1.000 + O4 O 0.04305 0.08090 0.24715 1.000 + C4 C 0.99546 0.25860 0.26511 1.000 + C4A C 0.72326 0.26070 0.65507 1.000 + C6 C 0.64494 0.10920 0.46864 1.000 + C7 C 0.71894 0.11040 0.35781 1.000 + C8 C 0.80704 0.94330 0.36004 1.000 + H8 H 0.80550 0.80040 0.38110 1.000 + C9 C 0.72975 0.98860 0.56772 1.000 + H9 H 0.76820 0.87510 0.59390 1.000 + C10 C 0.69872 0.20730 0.58931 1.000 + C11 C 0.61656 0.33130 0.48920 1.000 + H11 H 0.57940 0.44590 0.46280 1.000 + C12 C 0.72051 0.32720 0.32810 1.000 + H12 H 0.66120 0.43770 0.32710 1.000 + C13 C 0.81276 0.37560 0.29975 1.000 + H13 H 0.81610 0.52170 0.28030 1.000 + C14 C 0.89738 0.99290 0.33040 1.000 + H14 H 0.95610 0.88140 0.33090 1.000 + C15 C 0.70345 0.93900 0.50695 1.000 + H15 H 0.72490 0.79340 0.49220 1.000 + C16 C 0.90018 0.20830 0.30006 1.000 + C17 C 0.64454 0.37960 0.54979 1.000 + H17 H 0.62690 0.52870 0.56420 1.000 + O2 O 0.73242 0.47490 0.67168 1.000 + Ca1 Ca 0.13682 0.74415 0.22287 1.000 + Ca1 Ca 0.63682 0.75585 0.72287 1.000 + Ca1 Ca 0.86318 0.25585 0.77713 1.000 + S1 S 0.89835 0.53637 0.10768 1.000 + S1 S 0.39835 0.96363 0.60768 1.000 + S1 S 0.10165 0.46363 0.89232 1.000 + O6 O 0.99310 0.69500 0.13096 1.000 + O6 O 0.49310 0.80500 0.63096 1.000 + O6 O 0.00690 0.30500 0.86904 1.000 + O1 O 0.76226 0.58480 0.80936 1.000 + O1 O 0.26226 0.91520 0.30936 1.000 + O1 O 0.23774 0.41520 0.19064 1.000 + O5 O 0.91534 0.28110 0.11176 1.000 + O5 O 0.41534 0.21890 0.61176 1.000 + O5 O 0.08466 0.71890 0.88824 1.000 + O3 O 0.48423 0.97430 0.24378 1.000 + O3 O 0.98423 0.52570 0.74378 1.000 + O3 O 0.51577 0.02570 0.75622 1.000 + O4 O 0.45695 0.58090 0.25285 1.000 + O4 O 0.95695 0.91910 0.75285 1.000 + O4 O 0.54305 0.41910 0.74715 1.000 + C4 C 0.50454 0.75860 0.23489 1.000 + C4 C 0.00454 0.74140 0.73489 1.000 + C4 C 0.49546 0.24140 0.76511 1.000 + C4A C 0.77674 0.76070 0.84493 1.000 + C4A C 0.27674 0.73930 0.34493 1.000 + C4A C 0.22326 0.23930 0.15507 1.000 + C6 C 0.85506 0.60920 0.03136 1.000 + C6 C 0.35506 0.89080 0.53136 1.000 + C6 C 0.14494 0.39080 0.96864 1.000 + C7 C 0.78106 0.61040 0.14219 1.000 + C7 C 0.28106 0.88960 0.64219 1.000 + C7 C 0.21894 0.38960 0.85781 1.000 + C8 C 0.69296 0.44330 0.13996 1.000 + C8 C 0.19296 0.05670 0.63996 1.000 + C8 C 0.30704 0.55670 0.86004 1.000 + H8 H 0.69450 0.30040 0.11890 1.000 + H8 H 0.19450 0.19960 0.61890 1.000 + H8 H 0.30550 0.69960 0.88110 1.000 + C9 C 0.77025 0.48860 0.93228 1.000 + C9 C 0.27025 0.01140 0.43228 1.000 + C9 C 0.22975 0.51140 0.06772 1.000 + H9 H 0.73180 0.37510 0.90610 1.000 + H9 H 0.23180 0.12490 0.40610 1.000 + H9 H 0.26820 0.62490 0.09390 1.000 + C10 C 0.80128 0.70730 0.91069 1.000 + C10 C 0.30128 0.79270 0.41069 1.000 + C10 C 0.19872 0.29270 0.08931 1.000 + C11 C 0.88344 0.83130 0.01080 1.000 + C11 C 0.38344 0.66870 0.51080 1.000 + C11 C 0.11656 0.16870 0.98920 1.000 + H11 H 0.92060 0.94590 0.03720 1.000 + H11 H 0.42060 0.55410 0.53720 1.000 + H11 H 0.07940 0.05410 0.96280 1.000 + C12 C 0.77949 0.82720 0.17190 1.000 + C12 C 0.27949 0.67280 0.67190 1.000 + C12 C 0.22051 0.17280 0.82810 1.000 + H12 H 0.83880 0.93770 0.17290 1.000 + H12 H 0.33880 0.56230 0.67290 1.000 + H12 H 0.16120 0.06230 0.82710 1.000 + C13 C 0.68724 0.87560 0.20025 1.000 + C13 C 0.18724 0.62440 0.70025 1.000 + C13 C 0.31276 0.12440 0.79975 1.000 + H13 H 0.68390 0.02170 0.21970 1.000 + H13 H 0.18390 0.47830 0.71970 1.000 + H13 H 0.31610 0.97830 0.78030 1.000 + C14 C 0.60262 0.49290 0.16960 1.000 + C14 C 0.10262 0.00710 0.66960 1.000 + C14 C 0.39738 0.50710 0.83040 1.000 + H14 H 0.54390 0.38140 0.16910 1.000 + H14 H 0.04390 0.11860 0.66910 1.000 + H14 H 0.45610 0.61860 0.83090 1.000 + C15 C 0.79655 0.43900 0.99305 1.000 + C15 C 0.29655 0.06100 0.49305 1.000 + C15 C 0.20345 0.56100 0.00695 1.000 + H15 H 0.77510 0.29340 0.00780 1.000 + H15 H 0.27510 0.20660 0.50780 1.000 + H15 H 0.22490 0.70660 0.99220 1.000 + C16 C 0.59982 0.70830 0.19994 1.000 + C16 C 0.09982 0.79170 0.69994 1.000 + C16 C 0.40018 0.29170 0.80006 1.000 + C17 C 0.85546 0.87960 0.95021 1.000 + C17 C 0.35546 0.62040 0.45021 1.000 + C17 C 0.14454 0.12040 0.04979 1.000 + H17 H 0.87310 0.02870 0.93580 1.000 + H17 H 0.37310 0.47130 0.43580 1.000 + H17 H 0.12690 0.97130 0.06420 1.000 + O2 O 0.76758 0.97490 0.82832 1.000 + O2 O 0.26758 0.52510 0.32832 1.000 + O2 O 0.23242 0.02510 0.17168 1.000 From edf3aeec69058031eeebeeb04fbc0040479b6976 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 24 Jul 2019 12:19:01 -0700 Subject: [PATCH 008/105] add rounding to the isapprox for atoms and charges because it is no longer using isapprox for comparing xf's --- src/Matter.jl | 5 ++--- test/crystal_test.jl | 4 ---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Matter.jl b/src/Matter.jl index aebaf708f..7e3c4552b 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -21,8 +21,7 @@ end Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) Base.isapprox(a1::Atoms, a2::Atoms) = issetequal(Set(a1.species), Set(a2.species)) && - issetequal(Set([a1.xf[:, i] for i in 1:a1.n_atoms]), Set([a2.xf[:, i] for i in 1:a2.n_atoms])) - + issetequal(Set([round.(a1.xf[:, i], digits=5) for i in 1:a1.n_atoms]), Set([round.(a2.xf[:, i], digits=5) for i in 1:a2.n_atoms])) """ Data structure holds a set of point charges and their positions in fractional coordinates. @@ -46,4 +45,4 @@ end Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) Base.isapprox(c1::Charges, c2::Charges) = issetequal(Set(c1.q), Set(c2.q)) && - issetequal(Set([c1.xf[:, i] for i in 1:c1.n_charges]), Set([c2.xf[:, i] for i in 1:c2.n_charges])) + issetequal(Set([round.(c1.xf[:, i], digits=5) for i in 1:c1.n_charges]), Set([round.(c2.xf[:, i], digits=5) for i in 1:c2.n_charges])) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 539119e19..eec1e4d5a 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -39,10 +39,6 @@ using Random # openBabel non_P1_framework = Framework("KAXQIL_clean.cif") P1_framework = Framework("KAXQIL_clean_P1.cif") - # need to round to five digits because the equality fails when they have - # different significant digits - non_P1_framework.atoms.xf .= round.(non_P1_framework.atoms.xf, digits=5) - P1_framework.atoms.xf .= round.(P1_framework.atoms.xf, digits=5) @test isapprox(non_P1_framework, P1_framework) # test .cssr reader too; test_structure2.{cif,cssr} designed to be the same. From d0b6a40c5ae5d946795940dc0ce120c9306e77e1 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 24 Jul 2019 14:12:00 -0700 Subject: [PATCH 009/105] add atoms, charges, and framework concatenation, started framework unit test, need to find test for atoms and charges --- src/Crystal.jl | 19 +++++++++++++++++++ src/Matter.jl | 15 +++++++++++++++ test/crystal_test.jl | 28 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index 5a13a2a8c..ddd5f837a 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -826,3 +826,22 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false, ver end return box_flag && charges_flag && atoms_flag end + +function Base.:+(f1::Framework, f2::Framework) + @assert isapprox(f1.box, f2.box) "Two frameworks need the same Box to allow addition" + + new_atoms = f1.atoms + f2.atoms + new_charges = f1.charges + f2.charges + + new_framework = Framework(f1.name * " + " * f2.name, f1.box, new_atoms, new_charges) + + if atom_overlap(new_framework) + @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" + end + + if charge_overlap(new_framework) + @warn "This new framework has overlapping charges, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" + end + + return new_framework +end diff --git a/src/Matter.jl b/src/Matter.jl index 7e3c4552b..893017fe7 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -22,6 +22,14 @@ Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), spe Base.isapprox(a1::Atoms, a2::Atoms) = issetequal(Set(a1.species), Set(a2.species)) && issetequal(Set([round.(a1.xf[:, i], digits=5) for i in 1:a1.n_atoms]), Set([round.(a2.xf[:, i], digits=5) for i in 1:a2.n_atoms])) + +function Base.:+(a1::Atoms, a2::Atoms) + new_species = [a1.species; a2.species] + new_xf = [a1.xf a2.xf] + + return Atoms(a1.n_atoms + a2.n_atoms, new_species, new_xf) +end + """ Data structure holds a set of point charges and their positions in fractional coordinates. @@ -46,3 +54,10 @@ Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, x Base.isapprox(c1::Charges, c2::Charges) = issetequal(Set(c1.q), Set(c2.q)) && issetequal(Set([round.(c1.xf[:, i], digits=5) for i in 1:c1.n_charges]), Set([round.(c2.xf[:, i], digits=5) for i in 1:c2.n_charges])) + +function Base.:+(c1::Charges, c2::Charges) + new_q = [c1.q; c2.q] + new_xf = [c1.xf c2.xf] + + return Charges(c1.n_charges + c2.n_charges, new_q, new_xf) +end diff --git a/test/crystal_test.jl b/test/crystal_test.jl index eec1e4d5a..ca792fc94 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -59,6 +59,34 @@ using Random @test chemical_formula(sbmof) == chemical_formula(replicated_sbmof) @test isapprox(crystal_density(sbmof), crystal_density(replicated_sbmof), atol=1e-7) + # test framework addition + f1 = Framework("framework 1", UnitCube(), Atoms( + [:a, :b], + [1.0 4.0; + 2.0 5.0; + 3.0 6.0]), + Charges( + [0.1, 0.2], + [1.0 4.0; + 2.0 5.0; + 3.0 6.0])) + f2 = Framework("framework 2", UnitCube(), Atoms( + [:c, :d], + [7.0 10.0; + 8.0 11.0; + 9.0 12.0]), + Charges( + [0.3, 0.4], + [7.0 10.0; + 8.0 11.0; + 9.0 12.0])) + f3 = f1 + f2 + @test_throws AssertionError f1 + sbmof # only allow frameworks with same box + @test isapprox(f1.box, f3.box) + @test isapprox(f2.box, f3.box) + @test isapprox(f1.atoms + f2.atoms, f3.atoms) + @test isapprox(f1.charges + f2.charges, f3.charges) + # more xtal tests sbmof1 = Framework("SBMOF-1.cif") @test !charged(sbmof1) From f6fb964acca6bfdac210e6f044361787984fb8ca Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 24 Jul 2019 17:13:05 -0700 Subject: [PATCH 010/105] add tests for atoms and charges --- test/matter_test.jl | 35 +++++++++++++++++++++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 36 insertions(+) create mode 100644 test/matter_test.jl diff --git a/test/matter_test.jl b/test/matter_test.jl new file mode 100644 index 000000000..67dff4ebb --- /dev/null +++ b/test/matter_test.jl @@ -0,0 +1,35 @@ +module Matter_Test + +using PorousMaterials +using Random +using Test + +@testset "Matter Tests" begin + a1 = Atoms([:a, :b], [1.0 4.0; + 2.0 5.0; + 3.0 6.0]) + a2 = Atoms([:c, :d], [7.0 10.0; + 8.0 11.0; + 9.0 12.0]) + a3 = a1 + a2 + @test a3.n_atoms == a1.n_atoms + a2.n_atoms + @test issubset(Set(a1.xf), Set(a3.xf)) + @test issubset(Set(a2.xf), Set(a3.xf)) + @test issubset(Set(a1.species), Set(a3.species)) + @test issubset(Set(a2.species), Set(a3.species)) + + c1 = Charges([0.1, 0.2], [1.0 4.0; + 2.0 5.0; + 3.0 6.0]) + c2 = Charges([0.3, 0.4], [7.0 10.0; + 8.0 11.0; + 9.0 12.0]) + c3 = c1 + c2 + @test c3.n_charges == c1.n_charges + c2.n_charges + @test issubset(Set(c1.xf), Set(c3.xf)) + @test issubset(Set(c2.xf), Set(c3.xf)) + @test issubset(Set(c1.q), Set(c3.q)) + @test issubset(Set(c2.q), Set(c3.q)) + +end +end diff --git a/test/runtests.jl b/test/runtests.jl index be7cff947..2977a2bb2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ # Start Test Script include("box_test.jl") +include("matter_test.jl") include("crystal_test.jl") include("molecule_test.jl") include("nearest_image_test.jl") From 28af0d9a536c45814350a46dec61249a31dcd80f Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 24 Jul 2019 17:22:38 -0700 Subject: [PATCH 011/105] add more test for addition overloads in Matter.jl --- test/matter_test.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/matter_test.jl b/test/matter_test.jl index 67dff4ebb..b291a9bbe 100644 --- a/test/matter_test.jl +++ b/test/matter_test.jl @@ -5,6 +5,7 @@ using Random using Test @testset "Matter Tests" begin + # testing addition for atoms type a1 = Atoms([:a, :b], [1.0 4.0; 2.0 5.0; 3.0 6.0]) @@ -12,12 +13,18 @@ using Test 8.0 11.0; 9.0 12.0]) a3 = a1 + a2 + # total atoms in addition is number of atoms combined @test a3.n_atoms == a1.n_atoms + a2.n_atoms + # atoms from a1 & a2 should be within the atoms for a3 @test issubset(Set(a1.xf), Set(a3.xf)) @test issubset(Set(a2.xf), Set(a3.xf)) @test issubset(Set(a1.species), Set(a3.species)) @test issubset(Set(a2.species), Set(a3.species)) + # no atoms that weren't in a1 or a2 are in a3 + @test issetequal(setdiff(Set(a3.xf), union(Set(a1.xf), Set(a2.xf))), Set()) + @test issetequal(setdiff(Set(a3.species), union(Set(a1.species), Set(a2.species))), Set()) + # testing addition for charges type c1 = Charges([0.1, 0.2], [1.0 4.0; 2.0 5.0; 3.0 6.0]) @@ -25,11 +32,16 @@ using Test 8.0 11.0; 9.0 12.0]) c3 = c1 + c2 + # total charges in c3 must be the number of charges in c1 and c2 @test c3.n_charges == c1.n_charges + c2.n_charges + # charges from c1 & c2 should be within c3 @test issubset(Set(c1.xf), Set(c3.xf)) @test issubset(Set(c2.xf), Set(c3.xf)) @test issubset(Set(c1.q), Set(c3.q)) @test issubset(Set(c2.q), Set(c3.q)) + # no charges are added to c3 that weren't within c1 or c2 + @test issetequal(setdiff(Set(c3.xf), union(Set(c1.xf), Set(c2.xf))), Set()) + @test issetequal(setdiff(Set(c3.q), union(Set(c1.q), Set(c2.q))), Set()) end end From 305fa6dd3dfe629e036029dac1670d96f75fadd6 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 25 Jul 2019 10:46:12 -0700 Subject: [PATCH 012/105] add test for errors within .cif reader --- test/crystal_test.jl | 4 ++ test/data/crystals/no_atoms.cif | 36 +++++++++++++ test/data/crystals/non_P1_no_symmetry.cif | 66 +++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 test/data/crystals/no_atoms.cif create mode 100644 test/data/crystals/non_P1_no_symmetry.cif diff --git a/test/crystal_test.jl b/test/crystal_test.jl index ca792fc94..d22250114 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -40,6 +40,10 @@ using Random non_P1_framework = Framework("KAXQIL_clean.cif") P1_framework = Framework("KAXQIL_clean_P1.cif") @test isapprox(non_P1_framework, P1_framework) + # test that incorrect file formats throw proper errors + @test_throws ErrorException Framework("non_P1_no_symmetry.cif") + # test that a file with no atoms throws error + @test_throws ErrorException Framework("no_atoms.cif") # test .cssr reader too; test_structure2.{cif,cssr} designed to be the same. framework_from_cssr = Framework("test_structure2.cssr") diff --git a/test/data/crystals/no_atoms.cif b/test/data/crystals/no_atoms.cif new file mode 100644 index 000000000..e98b208ee --- /dev/null +++ b/test/data/crystals/no_atoms.cif @@ -0,0 +1,36 @@ + +####################################################################### +# +# Cambridge Crystallographic Data Centre +# CCDC +# +####################################################################### +# +# If this CIF has been generated from an entry in the Cambridge +# Structural Database, then it will include bibliographic, chemical, +# crystal, experimental, refinement or atomic coordinate data resulting +# from the CCDC's data processing and validation procedures. +# +####################################################################### + +data_casdb +_symmetry_cell_setting monoclinic +_symmetry_space_group_name_H-M 'P 21/n' +_symmetry_Int_Tables_number 14 +_space_group_name_Hall '-P 2yn' +loop_ +_symmetry_equiv_pos_site_id +_symmetry_equiv_pos_as_xyz +1 x,y,z +2 1/2-x,1/2+y,1/2-z +3 -x,-y,-z +4 1/2+x,1/2-y,1/2+z +_cell_length_a 11.8214(3) +_cell_length_b 5.56730(13) +_cell_length_c 22.7603(5) +_cell_angle_alpha 90.00 +_cell_angle_beta 100.356(2) +_cell_angle_gamma 90.00 +_cell_volume 1473.53 + +#END diff --git a/test/data/crystals/non_P1_no_symmetry.cif b/test/data/crystals/non_P1_no_symmetry.cif new file mode 100644 index 000000000..17f5aa09f --- /dev/null +++ b/test/data/crystals/non_P1_no_symmetry.cif @@ -0,0 +1,66 @@ + +####################################################################### +# +# Cambridge Crystallographic Data Centre +# CCDC +# +####################################################################### +# +# If this CIF has been generated from an entry in the Cambridge +# Structural Database, then it will include bibliographic, chemical, +# crystal, experimental, refinement or atomic coordinate data resulting +# from the CCDC's data processing and validation procedures. +# +####################################################################### + +data_casdb +_symmetry_cell_setting monoclinic +_symmetry_space_group_name_H-M 'P 21/n' +_symmetry_Int_Tables_number 14 +_space_group_name_Hall '-P 2yn' + +_cell_length_a 11.8214(3) +_cell_length_b 5.56730(13) +_cell_length_c 22.7603(5) +_cell_angle_alpha 90.00 +_cell_angle_beta 100.356(2) +_cell_angle_gamma 90.00 +_cell_volume 1473.53 +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +Ca1 Ca 0.36318(3) 0.24415(5) 0.277126(14) +S1 S 0.60165(3) 0.03637(7) 0.392320(16) +O6 O 0.50690(10) 0.1950(2) 0.36904(5) +O1 O 0.73774(11) 0.0848(2) 0.69064(5) +O5 O 0.58466(12) -0.2189(2) 0.38824(6) +O3 O 1.01577(12) 0.4743(2) 0.25622(6) +O4 O 1.04305(12) 0.0809(2) 0.24715(7) +C4 C 0.99546(14) 0.2586(3) 0.26511(8) +C4A C 0.72326(14) 0.2607(3) 0.65507(7) +C6 C 0.64494(14) 0.1092(3) 0.46864(7) +C7 C 0.71894(14) 0.1104(3) 0.35781(7) +C8 C 0.80704(16) -0.0567(3) 0.36004(8) +H8 H 0.8055 -0.1996 0.3811 +C9 C 0.72975(17) -0.0114(3) 0.56772(8) +H9 H 0.7682 -0.1249 0.5939 +C10 C 0.69872(14) 0.2073(3) 0.58931(7) +C11 C 0.61656(16) 0.3313(3) 0.48920(8) +H11 H 0.5794 0.4459 0.4628 +C12 C 0.72051(15) 0.3272(3) 0.32810(8) +H12 H 0.6612 0.4377 0.3271 +C13 C 0.81276(15) 0.3756(3) 0.29975(8) +H13 H 0.8161 0.5217 0.2803 +C14 C 0.89738(16) -0.0071(3) 0.33040(9) +H14 H 0.9561 -0.1186 0.3309 +C15 C 0.70345(18) -0.0610(3) 0.50695(8) +H15 H 0.7249 -0.2066 0.4922 +C16 C 0.90018(14) 0.2083(3) 0.30006(7) +C17 C 0.64454(16) 0.3796(3) 0.54979(8) +H17 H 0.6269 0.5287 0.5642 +O2 O 0.73242(11) 0.4749(2) 0.67168(6) + +#END From 0bce082308ebe5b98ccc686c85e6c47d229a2779 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 25 Jul 2019 13:11:37 -0700 Subject: [PATCH 013/105] remove debugging option for framework isapprox --- src/Crystal.jl | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index ddd5f837a..36726b1c5 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -807,7 +807,7 @@ function Base.show(io::IO, framework::Framework) println(io, "Chemical formula: ", chemical_formula(framework)) end -function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false, verbose::Bool=false) +function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) return false @@ -821,9 +821,6 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false, ver end charges_flag = isapprox(f1.charges, f2.charges) atoms_flag = isapprox(f1.atoms, f2.atoms) - if verbose - @printf("Box flag: %s\nAtoms flag: %s\nCharges flag: %s\n", box_flag ? "TRUE" : "FALSE", atoms_flag ? "TRUE" : "FALSE", charges_flag ? "TRUE" : "FALSE") - end return box_flag && charges_flag && atoms_flag end From 3a6c48b11e8aaa9ee7bd550a85481b611f42f94e Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 25 Jul 2019 16:18:01 -0700 Subject: [PATCH 014/105] change elementwise assignment for unit cell wrapping on line 232 and commented test line on 226 --- src/Crystal.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 36726b1c5..73f3c2cd9 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -223,13 +223,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, if !p1_symmetry && symmetry_info @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 to be ready for simulation use.", filename) for i in 1:size(symmetry_rules, 2) - # check for making sure the mapping of function was working correctly - #@printf("i: %d\tproof of mapping: %0.3f %0.3f %0.3f\n", i, Base.invokelatest.(symmetry_rules[:, i], [0.5, 0.5, 0.5])...) coords = [coords Base.invokelatest.(symmetry_rules[:, i], coords_simple)] charge_values = [charge_values; charges_simple] species = [species; species_simple] end - coords .= mod.(coords, 1.0) + coords = mod.(coords, 1.0) elseif p1_symmetry coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) From 8de67771ddd522e04932d807263aa4b0848275bb Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 25 Jul 2019 16:18:59 -0700 Subject: [PATCH 015/105] fix typo on line 38 --- test/crystal_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index d22250114..5d69fc62a 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -35,7 +35,7 @@ using Random # test .cif reader for non-P1 symmetry # no atoms should overlap - # should palce atoms in the same positions as the P1 conversion using + # should place atoms in the same positions as the P1 conversion using # openBabel non_P1_framework = Framework("KAXQIL_clean.cif") P1_framework = Framework("KAXQIL_clean_P1.cif") From b1295095acfb3a1647e07c57309c3ced94c0922c Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 25 Jul 2019 16:38:46 -0700 Subject: [PATCH 016/105] fix atoms/charges isapprox to match species/charge with location when checking if they are the same --- src/Matter.jl | 10 ++++++---- test/matter_test.jl | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Matter.jl b/src/Matter.jl index 893017fe7..e16f9a97f 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -20,8 +20,9 @@ end # compute n_species automatically from array sizes Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) -Base.isapprox(a1::Atoms, a2::Atoms) = issetequal(Set(a1.species), Set(a2.species)) && - issetequal(Set([round.(a1.xf[:, i], digits=5) for i in 1:a1.n_atoms]), Set([round.(a2.xf[:, i], digits=5) for i in 1:a2.n_atoms])) +Base.isapprox(a1::Atoms, a2::Atoms) = issetequal( + Set([(round.(a1.xf[:, i], digits=5), a1.species[i]) for i in 1:a1.n_atoms]), + Set([(round.(a2.xf[:, i], digits=5), a2.species[i]) for i in 1:a2.n_atoms])) function Base.:+(a1::Atoms, a2::Atoms) new_species = [a1.species; a2.species] @@ -52,8 +53,9 @@ end # compute n_charges automatically from array sizes Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) -Base.isapprox(c1::Charges, c2::Charges) = issetequal(Set(c1.q), Set(c2.q)) && - issetequal(Set([round.(c1.xf[:, i], digits=5) for i in 1:c1.n_charges]), Set([round.(c2.xf[:, i], digits=5) for i in 1:c2.n_charges])) +Base.isapprox(c1::Charges, c2::Charges) = issetequal( + Set([(round.(c1.xf[:, i], digits=5), c1.q[i]) for i in 1:c1.n_charges]), + Set([(round.(c2.xf[:, i], digits=5), c2.q[i]) for i in 1:c2.n_charges])) function Base.:+(c1::Charges, c2::Charges) new_q = [c1.q; c2.q] diff --git a/test/matter_test.jl b/test/matter_test.jl index b291a9bbe..bf87c5b9f 100644 --- a/test/matter_test.jl +++ b/test/matter_test.jl @@ -23,6 +23,10 @@ using Test # no atoms that weren't in a1 or a2 are in a3 @test issetequal(setdiff(Set(a3.xf), union(Set(a1.xf), Set(a2.xf))), Set()) @test issetequal(setdiff(Set(a3.species), union(Set(a1.species), Set(a2.species))), Set()) + a1_mismatch = Atoms([:b, :a], [1.0 4.0; + 2.0 5.0; + 3.0 6.0]) + @test ! isapprox(a1, a1_mismatch) # testing addition for charges type c1 = Charges([0.1, 0.2], [1.0 4.0; @@ -42,6 +46,10 @@ using Test # no charges are added to c3 that weren't within c1 or c2 @test issetequal(setdiff(Set(c3.xf), union(Set(c1.xf), Set(c2.xf))), Set()) @test issetequal(setdiff(Set(c3.q), union(Set(c1.q), Set(c2.q))), Set()) + c1_mismatch = Charges([0.2, 0.1], [1.0 4.0; + 2.0 5.0; + 3.0 6.0]) + @test ! isapprox(c1, c1_mismatch) end end From 63a570e9c36c3b33df1b68d504a33a1e9769abb4 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 30 Jul 2019 13:22:51 -0700 Subject: [PATCH 017/105] lower tolerance in matter.jl in order to have obabel and pmjl conversion be approximately equal. Now able to read ORIVOC.cif as is directly from ccdc, adjusted test to compare pmjl conversion to obabel conversion --- src/Crystal.jl | 28 +++++++++++++++------------- src/Matter.jl | 8 ++++---- test/crystal_test.jl | 4 ++-- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 73f3c2cd9..5239ac3d1 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -92,7 +92,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # checking for information about atom sites and symmetry if line[1] == "loop_" next_line = split(lines[i+1], [' ', '\t']; keepempty=false) - if occursin("_atom_site", next_line[1]) + if occursin("_atom_site", next_line[1][1:10]) atom_info = true atom_column_name = "" # name_to_column is a dictionary that e.g. returns which column contains x fractional coord @@ -143,7 +143,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, i += 1 loop_starts = i - while length(split(lines[i])) == 1 + while length(split(lines[i], [''', ' ', '\t', ','], keepempty=false)) == 1 name_to_column[split(lines[i])[1]] = i + 1 - loop_starts # iterate to next line in file i += 1 @@ -164,14 +164,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, new_sym_rule = Array{Function, 1}(undef, 3) - for sym in sym_funcs - if occursin("x", sym) - new_sym_rule[1] = eval(Meta.parse("x -> " * sym)) - elseif occursin("y", sym) - new_sym_rule[2] = eval(Meta.parse("y -> " * sym)) - elseif occursin("z", sym) - new_sym_rule[3] = eval(Meta.parse("z -> " * sym)) - end + sym_start = name_to_column["_symmetry_equiv_pos_as_xyz"] - 1 + for j = 1:3 + new_sym_rule[j] = eval(Meta.parse("(x, y, z) -> " * sym_funcs[j + sym_start])) end symmetry_rules = [symmetry_rules new_sym_rule] @@ -221,9 +216,14 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, γ = data["gamma"] if !p1_symmetry && symmetry_info - @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 to be ready for simulation use.", filename) + @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) + # loop over all symmetry rules for i in 1:size(symmetry_rules, 2) - coords = [coords Base.invokelatest.(symmetry_rules[:, i], coords_simple)] + new_col = Array{Float64, 1}(undef, 0) + # loop over all atom positions from lower level symmetry + for j in 1:size(coords_simple, 2) + coords = [coords [Base.invokelatest(symmetry_rules[k, i], coords_simple[:, j]...) for k in 1:3]] + end charge_values = [charge_values; charges_simple] species = [species; species_simple] end @@ -286,6 +286,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end end + strip_numbers_from_atom_labels!(framework) + if remove_overlap return remove_overlapping_atoms_and_charges(framework) end @@ -462,7 +464,7 @@ function remove_overlapping_atoms_and_charges(framework::Framework; atom_overlap_tol::Float64=0.1, charge_overlap_tol::Float64=0.1, verbose::Bool=true) atoms_to_keep = trues(framework.atoms.n_atoms) - charges_to_keep = trues(framework.atoms.n_atoms) + charges_to_keep = trues(framework.charges.n_charges) for i = 1:framework.atoms.n_atoms for j = 1:framework.atoms.n_atoms diff --git a/src/Matter.jl b/src/Matter.jl index e16f9a97f..b3cda63d9 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -21,8 +21,8 @@ end Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) Base.isapprox(a1::Atoms, a2::Atoms) = issetequal( - Set([(round.(a1.xf[:, i], digits=5), a1.species[i]) for i in 1:a1.n_atoms]), - Set([(round.(a2.xf[:, i], digits=5), a2.species[i]) for i in 1:a2.n_atoms])) + Set([(round.(a1.xf[:, i], digits=3), a1.species[i]) for i in 1:a1.n_atoms]), + Set([(round.(a2.xf[:, i], digits=3), a2.species[i]) for i in 1:a2.n_atoms])) function Base.:+(a1::Atoms, a2::Atoms) new_species = [a1.species; a2.species] @@ -54,8 +54,8 @@ end Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) Base.isapprox(c1::Charges, c2::Charges) = issetequal( - Set([(round.(c1.xf[:, i], digits=5), c1.q[i]) for i in 1:c1.n_charges]), - Set([(round.(c2.xf[:, i], digits=5), c2.q[i]) for i in 1:c2.n_charges])) + Set([(round.(c1.xf[:, i], digits=3), c1.q[i]) for i in 1:c1.n_charges]), + Set([(round.(c2.xf[:, i], digits=3), c2.q[i]) for i in 1:c2.n_charges])) function Base.:+(c1::Charges, c2::Charges) new_q = [c1.q; c2.q] diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 5d69fc62a..5ab604334 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -37,8 +37,8 @@ using Random # no atoms should overlap # should place atoms in the same positions as the P1 conversion using # openBabel - non_P1_framework = Framework("KAXQIL_clean.cif") - P1_framework = Framework("KAXQIL_clean_P1.cif") + non_P1_framework = Framework("ORIVOC_clean_fract.cif", remove_overlap=true) + P1_framework = Framework("ORIVOC_clean_P1.cif", remove_overlap=true) @test isapprox(non_P1_framework, P1_framework) # test that incorrect file formats throw proper errors @test_throws ErrorException Framework("non_P1_no_symmetry.cif") From 61953a81d670d4cf99676842014b668014263b00 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 30 Jul 2019 14:57:31 -0700 Subject: [PATCH 018/105] add testing files for ORIVOC, add functionality for reading in cartesian coordinates --- src/Crystal.jl | 36 ++++- src/Matter.jl | 8 +- test/crystal_test.jl | 5 + test/data/crystals/ORIVOC_clean.cif | 54 +++++++ test/data/crystals/ORIVOC_clean_P1.cif | 188 ++++++++++++++++++++++ test/data/crystals/ORIVOC_clean_fract.cif | 55 +++++++ 6 files changed, 339 insertions(+), 7 deletions(-) create mode 100644 test/data/crystals/ORIVOC_clean.cif create mode 100644 test/data/crystals/ORIVOC_clean_P1.cif create mode 100644 test/data/crystals/ORIVOC_clean_fract.cif diff --git a/src/Crystal.jl b/src/Crystal.jl index 5239ac3d1..62d2033c7 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -55,6 +55,10 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, zf = Array{Float64, 1}() coords = Array{Float64, 2}(undef, 3, 0) symmetry_rules = Array{Function, 2}(undef, 3, 0) + # used for remembering whether fractional/cartesian coordinates are read in + # placed here so it will be defined for the if-stmt after the box is defined + fractional = false + cartesian = false # Start of .cif reader @@ -65,6 +69,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, data = Dict{AbstractString, Float64}() loop_starts = -1 i = 1 + # used for reading in symmetry options and replications p1_symmetry = false symmetry_info = false atom_info = false @@ -110,15 +115,35 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, i += 1 end + # if the file provides fractional coordinates + fractional = haskey(name_to_column, "_atom_site_fract_x") && + haskey(name_to_column, "_atom_site_fract_y") && + haskey(name_to_column, "_atom_site_fract_z") + # if the file provides cartesian coordinates + cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && + haskey(name_to_column, "_atom_site_Cartn_y") && + haskey(name_to_column, "_atom_site_Cartn_z") && + ! fractional # if both are provided, will default + # to using fractional, so keep cartesian + # false + # read in atom_site info and store it in column based on # the name_to_column dictionary while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] + if fractional + coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] + elseif cartesian + coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] + else + error("The file does not store atom information in the form '_atom_site_fract_x' or '_atom_site_Cartn_x'") + end # If charges present, import them if haskey(name_to_column, "_atom_site_charge") push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) @@ -215,6 +240,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, β = data["beta"] γ = data["gamma"] + # redo coordinates if they were read in cartesian + if cartesian && ! fractional + coords_simple = Box(a, b, c, α, β, γ).c_to_f * coords_simple + end + if !p1_symmetry && symmetry_info @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) # loop over all symmetry rules diff --git a/src/Matter.jl b/src/Matter.jl index b3cda63d9..64187c594 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -21,8 +21,8 @@ end Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) Base.isapprox(a1::Atoms, a2::Atoms) = issetequal( - Set([(round.(a1.xf[:, i], digits=3), a1.species[i]) for i in 1:a1.n_atoms]), - Set([(round.(a2.xf[:, i], digits=3), a2.species[i]) for i in 1:a2.n_atoms])) + Set([(round.(a1.xf[:, i], digits=2), a1.species[i]) for i in 1:a1.n_atoms]), + Set([(round.(a2.xf[:, i], digits=2), a2.species[i]) for i in 1:a2.n_atoms])) function Base.:+(a1::Atoms, a2::Atoms) new_species = [a1.species; a2.species] @@ -54,8 +54,8 @@ end Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) Base.isapprox(c1::Charges, c2::Charges) = issetequal( - Set([(round.(c1.xf[:, i], digits=3), c1.q[i]) for i in 1:c1.n_charges]), - Set([(round.(c2.xf[:, i], digits=3), c2.q[i]) for i in 1:c2.n_charges])) + Set([(round.(c1.xf[:, i], digits=2), c1.q[i]) for i in 1:c1.n_charges]), + Set([(round.(c2.xf[:, i], digits=2), c2.q[i]) for i in 1:c2.n_charges])) function Base.:+(c1::Charges, c2::Charges) new_q = [c1.q; c2.q] diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 5ab604334..43f2f5052 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -38,8 +38,13 @@ using Random # should place atoms in the same positions as the P1 conversion using # openBabel non_P1_framework = Framework("ORIVOC_clean_fract.cif", remove_overlap=true) + non_P1_cartesian = Framework("ORIVOC_clean.cif", remove_overlap=true) P1_framework = Framework("ORIVOC_clean_P1.cif", remove_overlap=true) @test isapprox(non_P1_framework, P1_framework) + # test that fractional and cartesian produce same results + @test isapprox(non_P1_framework, non_P1_cartesian) + # test that cartesian and P1 produce same results + @test isapprox(non_P1_cartesian, P1_framework) # test that incorrect file formats throw proper errors @test_throws ErrorException Framework("non_P1_no_symmetry.cif") # test that a file with no atoms throws error diff --git a/test/data/crystals/ORIVOC_clean.cif b/test/data/crystals/ORIVOC_clean.cif new file mode 100644 index 000000000..85d93af75 --- /dev/null +++ b/test/data/crystals/ORIVOC_clean.cif @@ -0,0 +1,54 @@ +# CIF file generated by openbabel 2.3.2, see http://openbabel.sf.net +data_I +_chemical_name_common '' +_cell_length_a 26.179 +_cell_length_b 26.179 +_cell_length_c 6.65197 +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 120 +_space_group_name_H-M_alt 'R -3' +_space_group_name_Hall '-R 3' +loop_ + _symmetry_equiv_pos_as_xyz + 'x,y,z' + '-y,x-y,z' + '-x+y,-x,z' + '-x,-y,-z' + 'y,-x+y,-z' + 'x-y,x,-z' + '2/3+x,1/3+y,1/3+z' + '2/3-y,1/3+x-y,1/3+z' + '2/3-x+y,1/3-x,1/3+z' + '2/3-x,1/3-y,1/3-z' + '2/3+y,1/3-x+y,1/3-z' + '2/3+x-y,1/3+x,1/3-z' + '1/3+x,2/3+y,2/3+z' + '1/3-y,2/3+x-y,2/3+z' + '1/3-x+y,2/3-x,2/3+z' + '1/3-x,2/3-y,2/3-z' + '1/3+y,2/3-x+y,2/3-z' + '1/3+x-y,2/3+x,2/3-z' +loop_ + _atom_site_type_symbol + _atom_site_label + _atom_site_Cartn_x + _atom_site_Cartn_y + _atom_site_Cartn_z + Zn Zn1 5.56460 7.95050 1.01183 + C C2 5.78477 4.62184 1.91437 + C C3 6.09211 4.99683 0.60959 + C C4 6.85235 4.15345 -0.19610 + C C5 7.30459 2.93530 0.30293 + C C6 6.99711 2.56054 1.60771 + C C7 6.23727 3.40370 2.41347 + H H8 7.09097 4.44478 -1.20953 + H H9 5.99852 3.11259 3.42683 + C C10 4.96746 5.52871 2.78086 + O O11 4.55959 6.62692 2.33085 + O O12 4.69035 5.19067 3.95726 + C C13 8.12176 2.02866 -0.56349 + O O14 8.52963 0.93090 -0.11441 + O O15 8.39913 2.36669 -1.73996 + O O16 5.65426 6.17644 0.12639 + O O17 7.43535 1.38070 2.09138 diff --git a/test/data/crystals/ORIVOC_clean_P1.cif b/test/data/crystals/ORIVOC_clean_P1.cif new file mode 100644 index 000000000..5baccdd8c --- /dev/null +++ b/test/data/crystals/ORIVOC_clean_P1.cif @@ -0,0 +1,188 @@ +# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net +data_I +_chemical_name_common '' +_cell_length_a 26.179 +_cell_length_b 26.179 +_cell_length_c 6.65197 +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 120 +_space_group_name_H-M_alt 'P 1' +_space_group_name_Hall 'P 1' +loop_ + _symmetry_equiv_pos_as_xyz + x,y,z +loop_ + _atom_site_label + _atom_site_type_symbol + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + Zn1 Zn 0.38790 0.35068 0.15211 1.000 + C2 C 0.32290 0.20386 0.28779 1.000 + C3 C 0.34291 0.22040 0.09164 1.000 + C4 C 0.35335 0.18320 0.97052 1.000 + C5 C 0.34376 0.12947 0.04554 1.000 + C6 C 0.32375 0.11294 0.24169 1.000 + C7 C 0.31332 0.15013 0.36282 1.000 + H8 H 0.36889 0.19605 0.81817 1.000 + H9 H 0.29778 0.13729 0.51516 1.000 + C10 C 0.31168 0.24386 0.41805 1.000 + O11 O 0.32032 0.29230 0.35040 1.000 + O12 O 0.29364 0.22895 0.59490 1.000 + C13 C 0.35498 0.08948 0.91529 1.000 + O14 O 0.34635 0.04106 0.98280 1.000 + O15 O 0.37303 0.10439 0.73843 1.000 + O16 O 0.35220 0.27243 0.01900 1.000 + O17 O 0.31447 0.06090 0.31440 1.000 + Zn1 Zn 0.64932 0.03722 0.15211 1.000 + Zn1 Zn 0.96278 0.61210 0.15211 1.000 + Zn1 Zn 0.61210 0.64932 0.84789 1.000 + Zn1 Zn 0.35068 0.96278 0.84789 1.000 + Zn1 Zn 0.03722 0.38790 0.84789 1.000 + Zn1 Zn 0.05457 0.68401 0.48544 1.000 + Zn1 Zn 0.31599 0.37055 0.48544 1.000 + Zn1 Zn 0.62945 0.94543 0.48544 1.000 + Zn1 Zn 0.27877 0.98265 0.18122 1.000 + Zn1 Zn 0.01735 0.29611 0.18122 1.000 + Zn1 Zn 0.70389 0.72123 0.18122 1.000 + Zn1 Zn 0.72123 0.01735 0.81878 1.000 + Zn1 Zn 0.98265 0.70389 0.81878 1.000 + Zn1 Zn 0.29611 0.27877 0.81878 1.000 + Zn1 Zn 0.94543 0.31599 0.51456 1.000 + Zn1 Zn 0.68401 0.62945 0.51456 1.000 + Zn1 Zn 0.37055 0.05457 0.51456 1.000 + C2 C 0.79614 0.11904 0.28779 1.000 + C2 C 0.88096 0.67710 0.28779 1.000 + C2 C 0.67710 0.79614 0.71221 1.000 + C2 C 0.20386 0.88096 0.71221 1.000 + C2 C 0.11904 0.32290 0.71221 1.000 + C2 C 0.98957 0.53719 0.62112 1.000 + C2 C 0.46281 0.45237 0.62112 1.000 + C2 C 0.54763 0.01043 0.62112 1.000 + C2 C 0.87053 0.21429 0.04554 1.000 + C2 C 0.78571 0.65623 0.04554 1.000 + C2 C 0.65623 0.87053 0.95446 1.000 + C2 C 0.12947 0.78571 0.95446 1.000 + C2 C 0.21429 0.34377 0.95446 1.000 + C2 C 0.01043 0.46281 0.37888 1.000 + C2 C 0.53719 0.54763 0.37888 1.000 + C2 C 0.45237 0.98957 0.37888 1.000 + C3 C 0.77960 0.12251 0.09164 1.000 + C3 C 0.87749 0.65709 0.09164 1.000 + C3 C 0.65709 0.77960 0.90836 1.000 + C3 C 0.22040 0.87749 0.90836 1.000 + C3 C 0.12251 0.34291 0.90836 1.000 + C3 C 0.00958 0.55373 0.42497 1.000 + C3 C 0.44627 0.45584 0.42497 1.000 + C3 C 0.54416 0.99042 0.42497 1.000 + C3 C 0.88707 0.21082 0.24169 1.000 + C3 C 0.78918 0.67624 0.24169 1.000 + C3 C 0.67624 0.88707 0.75831 1.000 + C3 C 0.11293 0.78918 0.75831 1.000 + C3 C 0.21082 0.32376 0.75831 1.000 + C3 C 0.99042 0.44627 0.57503 1.000 + C3 C 0.55373 0.54416 0.57503 1.000 + C3 C 0.45584 0.00958 0.57503 1.000 + C4 C 0.81680 0.17015 0.97052 1.000 + C4 C 0.82985 0.64665 0.97052 1.000 + C4 C 0.64665 0.81680 0.02948 1.000 + C4 C 0.18320 0.82985 0.02948 1.000 + C4 C 0.17015 0.35335 0.02948 1.000 + C4 C 0.02002 0.51653 0.30385 1.000 + C4 C 0.48347 0.50348 0.30385 1.000 + C4 C 0.49652 0.97998 0.30385 1.000 + C4 C 0.84987 0.16318 0.36281 1.000 + C4 C 0.83682 0.68668 0.36281 1.000 + C4 C 0.68668 0.84987 0.63719 1.000 + C4 C 0.15013 0.83682 0.63719 1.000 + C4 C 0.16318 0.31332 0.63719 1.000 + C4 C 0.97998 0.48347 0.69615 1.000 + C4 C 0.51653 0.49652 0.69615 1.000 + C4 C 0.50348 0.02002 0.69615 1.000 + C7 C 0.35335 0.18320 0.97051 1.000 + H8 H 0.80395 0.17284 0.81817 1.000 + H8 H 0.82716 0.63111 0.81817 1.000 + H8 H 0.63111 0.80395 0.18183 1.000 + H8 H 0.19605 0.82716 0.18183 1.000 + H8 H 0.17284 0.36889 0.18183 1.000 + H8 H 0.03556 0.52938 0.15150 1.000 + H8 H 0.47062 0.50617 0.15150 1.000 + H8 H 0.49383 0.96444 0.15150 1.000 + H8 H 0.86272 0.16049 0.51516 1.000 + H8 H 0.83951 0.70222 0.51516 1.000 + H8 H 0.70222 0.86272 0.48484 1.000 + H8 H 0.13728 0.83951 0.48484 1.000 + H8 H 0.16049 0.29778 0.48484 1.000 + H8 H 0.96444 0.47062 0.84850 1.000 + H8 H 0.52938 0.49383 0.84850 1.000 + H8 H 0.50617 0.03556 0.84850 1.000 + H9 H 0.36889 0.19604 0.81817 1.000 + C10 C 0.75614 0.06782 0.41805 1.000 + C10 C 0.93218 0.68832 0.41805 1.000 + C10 C 0.68832 0.75614 0.58195 1.000 + C10 C 0.24386 0.93218 0.58195 1.000 + C10 C 0.06782 0.31168 0.58195 1.000 + C10 C 0.97835 0.57719 0.75138 1.000 + C10 C 0.42281 0.40115 0.75138 1.000 + C10 C 0.59885 0.02165 0.75138 1.000 + C10 C 0.35499 0.08947 0.91528 1.000 + C10 C 0.91053 0.26551 0.91528 1.000 + C10 C 0.73449 0.64501 0.91528 1.000 + C10 C 0.64501 0.91053 0.08472 1.000 + C10 C 0.08947 0.73449 0.08472 1.000 + C10 C 0.26551 0.35499 0.08472 1.000 + C10 C 0.02165 0.42281 0.24862 1.000 + C10 C 0.57719 0.59885 0.24862 1.000 + C10 C 0.40115 0.97835 0.24862 1.000 + O11 O 0.70770 0.02802 0.35040 1.000 + O11 O 0.97198 0.67968 0.35040 1.000 + O11 O 0.67968 0.70770 0.64960 1.000 + O11 O 0.29230 0.97198 0.64960 1.000 + O11 O 0.02802 0.32032 0.64960 1.000 + O11 O 0.98699 0.62563 0.68373 1.000 + O11 O 0.37437 0.36135 0.68373 1.000 + O11 O 0.63865 0.01301 0.68373 1.000 + O11 O 0.34635 0.04103 0.98293 1.000 + O11 O 0.95897 0.30531 0.98293 1.000 + O11 O 0.69469 0.65365 0.98293 1.000 + O11 O 0.65365 0.95897 0.01707 1.000 + O11 O 0.04103 0.69469 0.01707 1.000 + O11 O 0.30531 0.34635 0.01707 1.000 + O11 O 0.01301 0.37437 0.31627 1.000 + O11 O 0.62563 0.63865 0.31627 1.000 + O11 O 0.36135 0.98699 0.31627 1.000 + O12 O 0.77105 0.06469 0.59490 1.000 + O12 O 0.93531 0.70636 0.59490 1.000 + O12 O 0.70636 0.77105 0.40510 1.000 + O12 O 0.22895 0.93531 0.40510 1.000 + O12 O 0.06469 0.29364 0.40510 1.000 + O12 O 0.96031 0.56228 0.92823 1.000 + O12 O 0.43772 0.39802 0.92823 1.000 + O12 O 0.60198 0.03969 0.92823 1.000 + O12 O 0.37303 0.10438 0.73843 1.000 + O12 O 0.89562 0.26864 0.73843 1.000 + O12 O 0.73136 0.62697 0.73843 1.000 + O12 O 0.62697 0.89562 0.26157 1.000 + O12 O 0.10438 0.73136 0.26157 1.000 + O12 O 0.26864 0.37303 0.26157 1.000 + O12 O 0.03969 0.43772 0.07177 1.000 + O12 O 0.56228 0.60198 0.07177 1.000 + O12 O 0.39802 0.96031 0.07177 1.000 + O16 O 0.72757 0.07977 0.01900 1.000 + O16 O 0.92023 0.64780 0.01900 1.000 + O16 O 0.64780 0.72757 0.98100 1.000 + O16 O 0.27243 0.92023 0.98100 1.000 + O16 O 0.07977 0.35220 0.98100 1.000 + O16 O 0.01887 0.60576 0.35233 1.000 + O16 O 0.39424 0.41310 0.35233 1.000 + O16 O 0.58690 0.98113 0.35233 1.000 + O16 O 0.93910 0.25356 0.31433 1.000 + O16 O 0.74644 0.68553 0.31433 1.000 + O16 O 0.68553 0.93910 0.68567 1.000 + O16 O 0.06090 0.74644 0.68567 1.000 + O16 O 0.25356 0.31447 0.68567 1.000 + O16 O 0.98113 0.39424 0.64767 1.000 + O16 O 0.60576 0.58690 0.64767 1.000 + O16 O 0.41310 0.01887 0.64767 1.000 diff --git a/test/data/crystals/ORIVOC_clean_fract.cif b/test/data/crystals/ORIVOC_clean_fract.cif new file mode 100644 index 000000000..b1ab2bd2a --- /dev/null +++ b/test/data/crystals/ORIVOC_clean_fract.cif @@ -0,0 +1,55 @@ +# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net +data_I +_chemical_name_common '' +_cell_length_a 26.179 +_cell_length_b 26.179 +_cell_length_c 6.65197 +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 120 +_space_group_name_H-M_alt 'R -3' +_space_group_name_Hall '-R 3' +loop_ + _symmetry_equiv_pos_as_xyz + x,y,z + -y,x-y,z + -x+y,-x,z + -x,-y,-z + y,-x+y,-z + x-y,x,-z + 2/3+x,1/3+y,1/3+z + 2/3-y,1/3+x-y,1/3+z + 2/3-x+y,1/3-x,1/3+z + 2/3-x,1/3-y,1/3-z + 2/3+y,1/3-x+y,1/3-z + 2/3+x-y,1/3+x,1/3-z + 1/3+x,2/3+y,2/3+z + 1/3-y,2/3+x-y,2/3+z + 1/3-x+y,2/3-x,2/3+z + 1/3-x,2/3-y,2/3-z + 1/3+y,2/3-x+y,2/3-z + 1/3+x-y,2/3+x,2/3-z +loop_ + _atom_site_label + _atom_site_type_symbol + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + Zn1 Zn 0.38790 0.35068 0.15211 1.000 + C2 C 0.32290 0.20386 0.28779 1.000 + C3 C 0.34291 0.22040 0.09164 1.000 + C4 C 0.35335 0.18320 0.97052 1.000 + C5 C 0.34376 0.12947 0.04554 1.000 + C6 C 0.32375 0.11294 0.24169 1.000 + C7 C 0.31332 0.15013 0.36282 1.000 + H8 H 0.36889 0.19605 0.81817 1.000 + H9 H 0.29778 0.13729 0.51516 1.000 + C10 C 0.31168 0.24386 0.41805 1.000 + O11 O 0.32032 0.29230 0.35040 1.000 + O12 O 0.29364 0.22895 0.59490 1.000 + C13 C 0.35498 0.08948 0.91529 1.000 + O14 O 0.34635 0.04106 0.98280 1.000 + O15 O 0.37303 0.10439 0.73843 1.000 + O16 O 0.35220 0.27243 0.01900 1.000 + O17 O 0.31447 0.06090 0.31440 1.000 From eafbe0046a7a8773f162b9d27dccd9c30b538390 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 1 Aug 2019 14:15:26 -0700 Subject: [PATCH 019/105] don't automatically wrap all atoms to unti cell, change overlap (because some repeated atoms could be more than 1 unit cell away) towrap them to unit cell before checking --- test/crystal_test.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 43f2f5052..6d420359e 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -40,6 +40,17 @@ using Random non_P1_framework = Framework("ORIVOC_clean_fract.cif", remove_overlap=true) non_P1_cartesian = Framework("ORIVOC_clean.cif", remove_overlap=true) P1_framework = Framework("ORIVOC_clean_P1.cif", remove_overlap=true) + + # wrap all atoms and charges to be within the unit cell + non_P1_framework.atoms.xf .= mod.(non_P1_framework.atoms.xf, 1.0) + non_P1_framework.charges.xf .= mod.(non_P1_framework.charges.xf, 1.0) + + non_P1_cartesian.atoms.xf .= mod.(non_P1_cartesian.atoms.xf, 1.0) + non_P1_cartesian.charges.xf .= mod.(non_P1_cartesian.charges.xf, 1.0) + + P1_framework.atoms.xf .= mod.(P1_framework.atoms.xf, 1.0) + P1_framework.charges.xf .= mod.(P1_framework.charges.xf, 1.0) + @test isapprox(non_P1_framework, P1_framework) # test that fractional and cartesian produce same results @test isapprox(non_P1_framework, non_P1_cartesian) From faee05d4ecb60df8b1418e9bd1b37438dcf83172 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 1 Aug 2019 14:22:03 -0700 Subject: [PATCH 020/105] forgot changes to Crystal.jl in last commit --- src/Crystal.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 62d2033c7..896f17415 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -257,7 +257,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, charge_values = [charge_values; charges_simple] species = [species; species_simple] end - coords = mod.(coords, 1.0) elseif p1_symmetry coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) @@ -463,7 +462,7 @@ end # do overlap, and can then use that number to determine if they overlap or are repeats function _overlap(xf_1::Array{Float64, 1}, xf_2::Array{Float64, 1}, box::Box, overlap_tol::Float64) - dxf = xf_1 .- xf_2 + dxf = mod.(xf_1, 1.0) .- mod.(xf_2, 1.0) nearest_image!(dxf) dxc = box.f_to_c * dxf return norm(dxc) < overlap_tol From 4a56cffea87de79ce7750dbd1f072b3a084e261b Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 2 Aug 2019 11:32:02 -0700 Subject: [PATCH 021/105] store symmetry rules for a framework, and create function to apply symmetry rules --- src/Crystal.jl | 70 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 896f17415..da005d854 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -6,6 +6,7 @@ struct Framework box::Box atoms::Atoms charges::Charges + symmetry::Array{Function, 2} end """ @@ -35,7 +36,7 @@ or construct a `Framework` data structure directly. """ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, - remove_overlap::Bool=false) + remove_overlap::Bool=false, convert_to_p1::Bool=true) # Read file extension. Ensure we can read the file type extension = split(filename, ".")[end] if ! (extension in ["cif", "cssr"]) @@ -54,7 +55,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, yf = Array{Float64, 1}() zf = Array{Float64, 1}() coords = Array{Float64, 2}(undef, 3, 0) - symmetry_rules = Array{Function, 2}(undef, 3, 0) + # default for symmetry rules is P1. + # These will be overwritten if the user chooses to read in non-P1 + symmetry_rules = [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z] # used for remembering whether fractional/cartesian coordinates are read in # placed here so it will be defined for the if-stmt after the box is defined fractional = false @@ -233,6 +238,12 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, error("If structure is not in P1 symmetry it must have replication information") end + # warning that structure is not being converted to P1 symmetry + if ! convert_to_p1 && ! p1_symmetry + @warn @sprintf("%s is not in P1 symmetry and it is not being converted to P1 symmetry.\nAny simulations performed with PorousMaterials will NOT be accurate", + filename) + end + a = data["a"] b = data["b"] c = data["c"] @@ -245,7 +256,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, coords_simple = Box(a, b, c, α, β, γ).c_to_f * coords_simple end - if !p1_symmetry && symmetry_info + if symmetry_info && convert_to_p1 @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) # loop over all symmetry rules for i in 1:size(symmetry_rules, 2) @@ -257,7 +268,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, charge_values = [charge_values; charges_simple] species = [species; species_simple] end - elseif p1_symmetry + # has been converted to P1, so symmetry rules are set to P1 symmetry rules + symmetry_rules = [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z] + elseif p1_symmetry || !convert_to_p1 coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) species = deepcopy(species_simple) @@ -304,7 +319,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, idx_nz = charge_values .!= 0.0 charges = Charges(charge_values[idx_nz], coords[:, idx_nz]) - framework = Framework(filename, box, atoms, charges) + framework = Framework(filename, box, atoms, charges, symmetry_rules) if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) @@ -693,6 +708,51 @@ function crystal_density(framework::Framework) return mw / framework.box.Ω * 1660.53892 # --> kg/m3 end +""" + simulation_ready_framework = apply_symemtry_rules(non_p1_framework) + +Convert a framework to P1 symmetry based on internal symmetry rules. This will +return the new framework. + +# Arguments +- `f::Framework`: The framework to be converted to P1 symmetry + +# Returns +- `P1_framework::Framework`: The framework after it has been converted to P1 + symmetry. The new symmetry rules will be the P1 symemtry rules +""" +function apply_symmetry_rules(f::Framework) + new_atom_xfs = Array{Float64, 2}(undef, 3, 0) + new_charge_xfs = Array{Float64, 2}(undef, 3, 0) + new_atom_species = Array{Symbol, 1}(undef, 0) + new_charge_qs = Array{Float64, 1}(undef, 0) + + # for each symmetry rule + for i in 1:size(f.symmetry, 2) + # loop over all atoms in lower level symemtry + for j in 1:size(f.atoms.xf, 2) + # apply current symmetry rule to current atom for x, y, and z coordinates + new_atom_xfs = [new_atom_xfs [Base.invokelatest.(f.symmetry_rules[k, i], f.atoms.xf[:, j]...) for k in 1:3]] + end + # loop over all charges in lower level symmetry + for j in 1:size(f.charges.xf, 2) + # apply current symmetry rule to current atom for x, y, and z coordinates + new_charge_xfs = [new_charge_xfs [Base.invokelatest.(f.symmetry_rules[k, i], f.charges.xf[:, j]...) for k in 1:3]] + end + # repeat charge_qs and atom_species for every symmetry applied + new_atom_species = [new_atom_species f.atoms.species] + new_charge_qs = [new_charge_qs f.charges.q] + end + + new_symmetry_rules = [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z,] + + new_f = Framework(f.name, f.box, Atoms(new_atom_species, new_atom_xfs), Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules) + + return new_f +end + """ write_cif(framework, filename) From dbe274b1d06685e6a1d3bd3d812c6e2bedf4baf9 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 2 Aug 2019 13:05:57 -0700 Subject: [PATCH 022/105] add tests for comparing symmetry rules, still need test for applying symmetry rules --- src/Crystal.jl | 79 ++++++++++++++++++++++++++++++++++++------ src/PorousMaterials.jl | 1 + test/crystal_test.jl | 23 ++++++++++-- 3 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index da005d854..b6ece9827 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -57,9 +57,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, coords = Array{Float64, 2}(undef, 3, 0) # default for symmetry rules is P1. # These will be overwritten if the user chooses to read in non-P1 - symmetry_rules = [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z] + symmetry_rules = Array{Function, 2}(undef, 3, 0) # used for remembering whether fractional/cartesian coordinates are read in # placed here so it will be defined for the if-stmt after the box is defined fractional = false @@ -269,10 +267,13 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, species = [species; species_simple] end # has been converted to P1, so symmetry rules are set to P1 symmetry rules - symmetry_rules = [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z] + symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z]] elseif p1_symmetry || !convert_to_p1 + # leave symmetry rules the same + # if not P1, want to keep the full symmetry rules + # if P1 leave as is coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) species = deepcopy(species_simple) @@ -309,6 +310,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, for i = 1:n_atoms coords = [ coords [xf[i], yf[i], zf[i]] ] end + + # add P1 symmetry rules for consistency + symmetry_rules = [symmetry_rules [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z]] end # Construct the unit cell box @@ -393,7 +399,7 @@ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) @assert (new_charges.n_charges == framework.charges.n_charges * prod(repfactors)) @assert (new_atoms.n_atoms == framework.atoms.n_atoms * prod(repfactors)) - return Framework(framework.name, new_box, new_atoms, new_charges) + return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry)) end # doc string in Misc.jl @@ -568,7 +574,7 @@ function remove_overlapping_atoms_and_charges(framework::Framework; atoms = Atoms(framework.atoms.species[atoms_to_keep], atom_coords_to_keep) charges = Charges(framework.charges.q[charges_to_keep], charge_coords_to_keep) - new_framework = Framework(framework.name, framework.box, atoms, charges) + new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry)) @assert (! atom_overlap(new_framework, overlap_tol=atom_overlap_tol)) @assert (! charge_overlap(new_framework, overlap_tol=charge_overlap_tol)) @@ -753,6 +759,56 @@ function apply_symmetry_rules(f::Framework) return new_f end +""" + symmetry_equal = is_symmetry_equal(sym1, sym2) + +Returns true if both symmetry rules can create the same set from the same set +of coordinates. Returns false if they don't contain the same number of rules or +if they create different sets of points. + +# Arguments +- `sym1::Array{Function, 2}`: Array of anonymous functions that represent + symmetry operations +- `sym2::Array{Function, 2}`: Array of anonymous functions that represent + symmetry operations + +# Returns +- `is_equal::Bool`: True if they are the same set of symmetry rules + False if they are different +""" +function is_symmetry_equal(sym1::Array{Function, 2}, sym2::Array{Function, 2}) + # need same number of symmetry operations + if size(sym1, 2) != size(sym2, 2) + return false + end + # define a test array that operations will be performed on + test_array = [0.0 0.25 0.0 0.0 0.0 0.25 0.25 0.25; + 0.0 0.0 0.25 0.0 0.25 0.0 0.25 0.25; + 0.0 0.0 0.0 0.25 0.25 0.25 0.25 0.25] + # set up both arrays for storing replicated coords + sym1_applied_to_test = Array{Float64, 2}(undef, 3, 0) + sym2_applied_to_test = Array{Float64, 2}(undef, 3, 0) + + # loop over all positions in the test_array + for i in 1:size(test_array, 2) + # loop over f1 symmetry rules + for j in 1:size(sym1, 2) + sym1_applied_to_test = [sym1_applied_to_test [Base.invokelatest.(sym1[k, j], test_array[:, i]...) for k in 1:3]] + end + # loop over f2 symmetry rules + for j in 1:size(sym2, 2) + sym2_applied_to_test = [sym2_applied_to_test [Base.invokelatest.(sym2[k, j], test_array[:, i]...) for k in 1:3]] + end + end + + # convert to sets for using issetequal, symmetry rules might be in a a different order + sym1_set = Set([sym1_applied_to_test[:, i] for i in 1:size(sym1_applied_to_test, 2)]) + sym2_set = Set([sym2_applied_to_test[:, i] for i in 1:size(sym2_applied_to_test, 2)]) + + # return if the sets of coords are equal + return issetequal(sym1_set, sym2_set) +end + """ write_cif(framework, filename) @@ -876,7 +932,7 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 charges = Charges(charge_vals, charge_coords) # construct new framework - new_framework = Framework(framework.name, framework.box, framework.atoms, charges) + new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry)) # check for charge neutrality if abs(total_charge(new_framework)) > net_charge_tol @@ -896,6 +952,7 @@ function Base.show(io::IO, framework::Framework) println(io, "Chemical formula: ", chemical_formula(framework)) end +# TODO add something comparing symmetry rules function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) @@ -913,13 +970,15 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) return box_flag && charges_flag && atoms_flag end +# TODO do something about symmetry rules, force to have same function Base.:+(f1::Framework, f2::Framework) @assert isapprox(f1.box, f2.box) "Two frameworks need the same Box to allow addition" + @assert is_symmetry_equal(f1.symmetry, f2.symmetry) "Symmetry rules are different" new_atoms = f1.atoms + f2.atoms new_charges = f1.charges + f2.charges - new_framework = Framework(f1.name * " + " * f2.name, f1.box, new_atoms, new_charges) + new_framework = Framework(f1.name * "_" * f2.name, f1.box, new_atoms, new_charges, f1.symmetry) if atom_overlap(new_framework) @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 990d8c426..0d19e9d93 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -113,6 +113,7 @@ export Framework, read_crystal_structure_file, remove_overlapping_atoms_and_charges, strip_numbers_from_atom_labels!, chemical_formula, molecular_weight, crystal_density, construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, + is_symmetry_equal, apply_symmetry_rules, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 6d420359e..9577b83f5 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -79,6 +79,23 @@ using Random @test chemical_formula(sbmof) == chemical_formula(replicated_sbmof) @test isapprox(crystal_density(sbmof), crystal_density(replicated_sbmof), atol=1e-7) + # test symmetry_rules + # define default symmetry_rules + symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z]] + other_symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> y + z, + (x, y, z) -> x + z, + (x, y, z) -> x + y]] + symmetry_rules_two = [(x, y, z) -> x (x, y, z) -> y + z; + (x, y, z) -> y (x, y, z) -> x + z; + (x, y, z) -> z (x, y, z) -> x + y] + symmetry_rules_two_cpy = deepcopy(symmetry_rules_two) + @test ! is_symmetry_equal(symmetry_rules, symmetry_rules_two) + @test ! is_symmetry_equal(symmetry_rules, other_symmetry_rules) + @test is_symmetry_equal(symmetry_rules, symmetry_rules) + @test is_symmetry_equal(symmetry_rules_two, symmetry_rules_two_cpy) + # test framework addition f1 = Framework("framework 1", UnitCube(), Atoms( [:a, :b], @@ -89,7 +106,8 @@ using Random [0.1, 0.2], [1.0 4.0; 2.0 5.0; - 3.0 6.0])) + 3.0 6.0]), + deepcopy(symmetry_rules)) f2 = Framework("framework 2", UnitCube(), Atoms( [:c, :d], [7.0 10.0; @@ -99,7 +117,8 @@ using Random [0.3, 0.4], [7.0 10.0; 8.0 11.0; - 9.0 12.0])) + 9.0 12.0]), + deepcopy(symmetry_rules)) f3 = f1 + f2 @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) From bda5d72dc65e1b4fe511b6ba7004d99516eef7e9 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 2 Aug 2019 13:42:49 -0700 Subject: [PATCH 023/105] made unit tests for applying symmetry after reading. need to resolve test that fail from symmetry rules (something already in P1 or .cssr) --- src/Crystal.jl | 41 ++++++++++++++++++++++++++++++++--------- test/crystal_test.jl | 20 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index b6ece9827..3043532bf 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -727,7 +727,9 @@ return the new framework. - `P1_framework::Framework`: The framework after it has been converted to P1 symmetry. The new symmetry rules will be the P1 symemtry rules """ -function apply_symmetry_rules(f::Framework) +function apply_symmetry_rules(f::Framework; check_charge_neutrality::Bool=true, + net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, + remove_overlap::Bool=false) new_atom_xfs = Array{Float64, 2}(undef, 3, 0) new_charge_xfs = Array{Float64, 2}(undef, 3, 0) new_atom_species = Array{Symbol, 1}(undef, 0) @@ -738,24 +740,44 @@ function apply_symmetry_rules(f::Framework) # loop over all atoms in lower level symemtry for j in 1:size(f.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_atom_xfs = [new_atom_xfs [Base.invokelatest.(f.symmetry_rules[k, i], f.atoms.xf[:, j]...) for k in 1:3]] + new_atom_xfs = [new_atom_xfs [Base.invokelatest.(f.symmetry[k, i], f.atoms.xf[:, j]...) for k in 1:3]] end # loop over all charges in lower level symmetry for j in 1:size(f.charges.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_charge_xfs = [new_charge_xfs [Base.invokelatest.(f.symmetry_rules[k, i], f.charges.xf[:, j]...) for k in 1:3]] + new_charge_xfs = [new_charge_xfs [Base.invokelatest.(f.symmetry[k, i], f.charges.xf[:, j]...) for k in 1:3]] end # repeat charge_qs and atom_species for every symmetry applied - new_atom_species = [new_atom_species f.atoms.species] - new_charge_qs = [new_charge_qs f.charges.q] + new_atom_species = [new_atom_species; f.atoms.species] + new_charge_qs = [new_charge_qs; f.charges.q] end - new_symmetry_rules = [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z,] + new_symmetry_rules = [Array{Float64, 2}(undef, 3, 0) [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z]] new_f = Framework(f.name, f.box, Atoms(new_atom_species, new_atom_xfs), Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules) + if check_charge_neutrality + if ! charge_neutral(new_f, net_charge_tol) + error(@sprintf("Framework %s is not charge neutral; net charge is %f e. Ignore + this error message by passing check_charge_neutrality=false or increasing the + net charge tolerance `net_charge_tol`\n", + new_f.name, total_charge(new_f))) + end + end + + if remove_overlap + return remove_overlapping_atoms_and_charges(new_f) + end + + if check_atom_and_charge_overlap + if atom_overlap(new_f) | charge_overlap(new_f) + error(@sprintf("At least one pair of atoms/charges overlap in %s. + Consider passing `remove_overlap=true`\n", new_f.name)) + end + end + return new_f end @@ -967,7 +989,8 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) end charges_flag = isapprox(f1.charges, f2.charges) atoms_flag = isapprox(f1.atoms, f2.atoms) - return box_flag && charges_flag && atoms_flag + symmetry_flag = is_symmetry_equal(f1.symmetry, f2.symmetry) + return box_flag && charges_flag && atoms_flag && symmetry_flag end # TODO do something about symmetry rules, force to have same diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 9577b83f5..a516e08fa 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -61,6 +61,26 @@ using Random # test that a file with no atoms throws error @test_throws ErrorException Framework("no_atoms.cif") + # test reading in non-P1 then applying symmetry later + # read in the same files as above, then convert to P1, then compare + non_P1_framework_symmetry = Framework("ORIVOC_clean_fract.cif", convert_to_p1=false) + non_P1_cartesian_symmetry = Framework("ORIVOC_clean.cif", convert_to_p1=false) + + non_P1_framework_symmetry = apply_symmetry_rules(non_P1_framework_symmetry, remove_overlap=true) + non_P1_cartesian_symmetry = apply_symmetry_rules(non_P1_cartesian_symmetry, remove_overlap=true) + + # wrap all atoms and charges to be within the unit cell + non_P1_framework_symmetry.atoms.xf .= mod.(non_P1_framework_symmetry.atoms.xf, 1.0) + non_P1_framework_symmetry.charges.xf .= mod.(non_P1_framework_symmetry.charges.xf, 1.0) + + non_P1_cartesian_symmetry.atoms.xf .= mod.(non_P1_cartesian_symmetry.atoms.xf, 1.0) + non_P1_cartesian_symmetry.charges.xf .= mod.(non_P1_cartesian_symmetry.charges.xf, 1.0) + + # test that same structure is created when reading and converting to P1 and + # when reading then converting to P1 + @test isapprox(non_P1_framework, non_P1_framework_symmetry) + @test isapprox(non_P1_cartesian, non_P1_cartesian_symmetry) + # test .cssr reader too; test_structure2.{cif,cssr} designed to be the same. framework_from_cssr = Framework("test_structure2.cssr") strip_numbers_from_atom_labels!(framework_from_cssr) From 00558d9fcf72cc860b38f5774a67ede426c63399 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 2 Aug 2019 13:48:47 -0700 Subject: [PATCH 024/105] all tests pass for converting to P1 and comparing symmetries --- src/Crystal.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 3043532bf..8e8a7de6e 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -266,19 +266,19 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, charge_values = [charge_values; charges_simple] species = [species; species_simple] end - # has been converted to P1, so symmetry rules are set to P1 symmetry rules - symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z]] elseif p1_symmetry || !convert_to_p1 - # leave symmetry rules the same - # if not P1, want to keep the full symmetry rules - # if P1 leave as is coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) species = deepcopy(species_simple) end + # either read in P1 or converted to P1 so should have same symmetry rules + if p1_symmetry || convert_to_p1 + symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, + (x, y, z) -> y, + (x, y, z) -> z]] + end + # Start of cssr reader #TODO make sure this works for different .cssr files! elseif extension == "cssr" # First line contains unit cell lenghts From 125127bee68950b4845e7f8b8d350e54ce17cfab Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 11:04:58 -0700 Subject: [PATCH 025/105] add flag for whether the framework is in P1 symmetry, add test for checking it --- src/Crystal.jl | 20 +++++++++++++------- test/crystal_test.jl | 19 +++++++++++++++++-- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 8e8a7de6e..8adc995ca 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -7,6 +7,7 @@ struct Framework atoms::Atoms charges::Charges symmetry::Array{Function, 2} + is_p1::Bool end """ @@ -62,6 +63,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # placed here so it will be defined for the if-stmt after the box is defined fractional = false cartesian = false + # used for determining if the framework is in P1 symmetry for simulations + p1_symmetry = false # Start of .cif reader @@ -73,7 +76,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, loop_starts = -1 i = 1 # used for reading in symmetry options and replications - p1_symmetry = false symmetry_info = false atom_info = false while i <= length(lines) @@ -279,6 +281,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, (x, y, z) -> z]] end + # if structure was stored in P1 or converted to P1, store that information for later + p1_symmetry = p1_symmetry || convert_to_p1 + # Start of cssr reader #TODO make sure this works for different .cssr files! elseif extension == "cssr" # First line contains unit cell lenghts @@ -315,6 +320,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, symmetry_rules = [symmetry_rules [(x, y, z) -> x, (x, y, z) -> y, (x, y, z) -> z]] + p1_symmetry = true end # Construct the unit cell box @@ -325,7 +331,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, idx_nz = charge_values .!= 0.0 charges = Charges(charge_values[idx_nz], coords[:, idx_nz]) - framework = Framework(filename, box, atoms, charges, symmetry_rules) + framework = Framework(filename, box, atoms, charges, symmetry_rules, p1_symmetry) if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) @@ -399,7 +405,7 @@ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) @assert (new_charges.n_charges == framework.charges.n_charges * prod(repfactors)) @assert (new_atoms.n_atoms == framework.atoms.n_atoms * prod(repfactors)) - return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry)) + return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry), framework.is_p1) end # doc string in Misc.jl @@ -574,7 +580,7 @@ function remove_overlapping_atoms_and_charges(framework::Framework; atoms = Atoms(framework.atoms.species[atoms_to_keep], atom_coords_to_keep) charges = Charges(framework.charges.q[charges_to_keep], charge_coords_to_keep) - new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry)) + new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry), framework.is_p1) @assert (! atom_overlap(new_framework, overlap_tol=atom_overlap_tol)) @assert (! charge_overlap(new_framework, overlap_tol=charge_overlap_tol)) @@ -756,7 +762,7 @@ function apply_symmetry_rules(f::Framework; check_charge_neutrality::Bool=true, (x, y, z) -> y, (x, y, z) -> z]] - new_f = Framework(f.name, f.box, Atoms(new_atom_species, new_atom_xfs), Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules) + new_f = Framework(f.name, f.box, Atoms(new_atom_species, new_atom_xfs), Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, true) if check_charge_neutrality if ! charge_neutral(new_f, net_charge_tol) @@ -954,7 +960,7 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 charges = Charges(charge_vals, charge_coords) # construct new framework - new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry)) + new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry), framework.is_p1) # check for charge neutrality if abs(total_charge(new_framework)) > net_charge_tol @@ -1001,7 +1007,7 @@ function Base.:+(f1::Framework, f2::Framework) new_atoms = f1.atoms + f2.atoms new_charges = f1.charges + f2.charges - new_framework = Framework(f1.name * "_" * f2.name, f1.box, new_atoms, new_charges, f1.symmetry) + new_framework = Framework(f1.name * "_" * f2.name, f1.box, new_atoms, new_charges, f1.symmetry, f1.is_p1) if atom_overlap(new_framework) @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" diff --git a/test/crystal_test.jl b/test/crystal_test.jl index a516e08fa..4c389949b 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -60,12 +60,21 @@ using Random @test_throws ErrorException Framework("non_P1_no_symmetry.cif") # test that a file with no atoms throws error @test_throws ErrorException Framework("no_atoms.cif") + # test that these carry the is_p1 flag + @test non_P1_framework.is_p1 + @test non_P1_cartesian.is_p1 + @test P1_framework.is_p1 # test reading in non-P1 then applying symmetry later # read in the same files as above, then convert to P1, then compare non_P1_framework_symmetry = Framework("ORIVOC_clean_fract.cif", convert_to_p1=false) non_P1_cartesian_symmetry = Framework("ORIVOC_clean.cif", convert_to_p1=false) + # make sure these frameworks are not in P1 symmetry when convert_to_p1 is + # set to false + @test ! non_P1_framework_symmetry.is_p1 + @test ! non_P1_cartesian_symmetry.is_p1 + non_P1_framework_symmetry = apply_symmetry_rules(non_P1_framework_symmetry, remove_overlap=true) non_P1_cartesian_symmetry = apply_symmetry_rules(non_P1_cartesian_symmetry, remove_overlap=true) @@ -76,6 +85,10 @@ using Random non_P1_cartesian_symmetry.atoms.xf .= mod.(non_P1_cartesian_symmetry.atoms.xf, 1.0) non_P1_cartesian_symmetry.charges.xf .= mod.(non_P1_cartesian_symmetry.charges.xf, 1.0) + # make sure frameworks are now recorded as being in P1 with their is_p1 flag + @test non_P1_framework_symmetry.is_p1 + @test non_P1_cartesian_symmetry.is_p1 + # test that same structure is created when reading and converting to P1 and # when reading then converting to P1 @test isapprox(non_P1_framework, non_P1_framework_symmetry) @@ -127,7 +140,8 @@ using Random [1.0 4.0; 2.0 5.0; 3.0 6.0]), - deepcopy(symmetry_rules)) + deepcopy(symmetry_rules), + true) f2 = Framework("framework 2", UnitCube(), Atoms( [:c, :d], [7.0 10.0; @@ -138,7 +152,8 @@ using Random [7.0 10.0; 8.0 11.0; 9.0 12.0]), - deepcopy(symmetry_rules)) + deepcopy(symmetry_rules), + true) f3 = f1 + f2 @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) From 3a51a86ad96b44270669ecc916eeb1fd082cfb22 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 11:26:33 -0700 Subject: [PATCH 026/105] add assertions for GCMC and Henry coefficient tests. create new test file for these assertions --- src/GCMC.jl | 23 +++++++++++++++++++++++ src/Henry.jl | 8 ++++++++ test/runtests.jl | 1 + test/simulation_rules_test.jl | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 test/simulation_rules_test.jl diff --git a/src/GCMC.jl b/src/GCMC.jl index 71ab230ce..e2b4c34ef 100644 --- a/src/GCMC.jl +++ b/src/GCMC.jl @@ -157,6 +157,14 @@ function stepwise_adsorption_isotherm(framework::Framework, density_grid_dx::Float64=1.0, density_grid_species::Union{Nothing, Symbol}=nothing, filename_comment::AbstractString="") + + # simulation only works if framework is in P1 + @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n + try running:\n + \tframework_p1 = apply_symmetry_rules(framework)\n + and pass `framework_p1` into this simulation", + framework.name) + results = Dict{String, Any}[] # push results to this array molecules = Molecule[] # initiate with empty framework for (i, pressure) in enumerate(pressures) @@ -213,6 +221,14 @@ function adsorption_isotherm(framework::Framework, density_grid_dx::Float64=1.0, density_grid_species::Union{Nothing, Symbol}=nothing, filename_comment::AbstractString="") + + # simulation only works if framework is in P1 + @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n + try running:\n + \tframework_p1 = apply_symmetry_rules(framework)\n + and pass `framework_p1` into this simulation", + framework.name) + # make a function of pressure only to facilitate uses of `pmap` run_pressure(pressure::Float64) = gcmc_simulation(framework, molecule, temperature, pressure, ljforcefield, @@ -307,6 +323,13 @@ function gcmc_simulation(framework::Framework, molecule_::Molecule, temperature: calculate_density_grid::Bool=false, density_grid_dx::Float64=1.0, density_grid_species::Union{Nothing, Symbol}=nothing, filename_comment::AbstractString="") + # simulation only works if framework is in P1 + @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n + try running:\n + \tframework_p1 = apply_symmetry_rules(framework)\n + and pass `framework_p1` into this simulation", + framework.name) + start_time = time() # to avoid changing the outside object `molecule_` inside this function, we make # a deep copy of it here. this serves as a template to copy when we insert a new molecule. diff --git a/src/Henry.jl b/src/Henry.jl index a08216944..1be5ce006 100644 --- a/src/Henry.jl +++ b/src/Henry.jl @@ -46,6 +46,14 @@ function henry_coefficient(framework::Framework, molecule_::Molecule, temperatur write_checkpoint::Bool=false, load_checkpoint::Bool=false, checkpoint_frequency::Int=10000, accessibility_grid::Union{Nothing, Grid{Bool}}=nothing) + + # simulation only works if framework is in P1 + @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n + try running:\n + \tframework_p1 = apply_symmetry_rules(framework)\n + and pass `framework_p1` into this simulation", + framework.name) + time_start = time() if verbose print("Simulating Henry coefficient of ") diff --git a/test/runtests.jl b/test/runtests.jl index 2977a2bb2..2233e2176 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,6 +13,7 @@ include("electrostatics_energetics_test.jl") include("mchelpers_test.jl") include("guest_guest_energetics_test.jl") include("eos_test.jl") +include("simulation_rules_test.jl") include("gcmc_checkpoints_test.jl") include("henry_checkpoint_test.jl") include("grid_test.jl") diff --git a/test/simulation_rules_test.jl b/test/simulation_rules_test.jl new file mode 100644 index 000000000..a6f5faca4 --- /dev/null +++ b/test/simulation_rules_test.jl @@ -0,0 +1,33 @@ +module Simulation_Rules_Test + +using PorousMaterials +using Test + +# Test set for making sure simulations meet certain rules +# i.e. frameworks musts be in P1 symmetry to be used in GCMC or Henry +# coefficient test +@testset "Simulation Rules Tests" begin + non_P1_framework = Framework("ORIVOC.cif", convert_to_p1=false) + molecule = Molecule("CO2") + ljff = LJForceField("UFF.csv") + temp = 298.0 + pressure = 5.0 + + # make sure that the framework is not in P1 before running tests + + # assertion errors should be thrown when trying to run gcmc simulations + # with non-P1 frameworks + @test_throws AssertionError gcmc_simulation(non_P1_framework, molecule, + temp, pressure, ljff) + pressures = [1.0, 5.0, 10.0, 15.0, 20.0] + @test_throws AssertionError adsorption_isotherm(non_P1_framework, molecule, + temp, pressures, ljff) + @test_throws AssertionError stepwise_adsorption_isotherm(non_P1_framework, + molecule, temp, pressures, ljff) + + # assertion error should be thrown when attempting to run a henry + # coefficient with a non-P1 framework + @test_throws AssertionError henry_coefficient(non_P1_framework, molecule, + temp, ljff) +end +end From 4089c4dd39216ac1efc055ce84cc76443cfd7ee1 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 11:44:45 -0700 Subject: [PATCH 027/105] change 'ORIVOC.cif' to 'ORIVOC_clean.cif' because 'ORIVOC.cif' is not in the repo --- test/simulation_rules_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/simulation_rules_test.jl b/test/simulation_rules_test.jl index a6f5faca4..bd27e69a5 100644 --- a/test/simulation_rules_test.jl +++ b/test/simulation_rules_test.jl @@ -7,7 +7,7 @@ using Test # i.e. frameworks musts be in P1 symmetry to be used in GCMC or Henry # coefficient test @testset "Simulation Rules Tests" begin - non_P1_framework = Framework("ORIVOC.cif", convert_to_p1=false) + non_P1_framework = Framework("ORIVOC_clean.cif", convert_to_p1=false) molecule = Molecule("CO2") ljff = LJForceField("UFF.csv") temp = 298.0 From 136d41f2ed4980a1c60c6737dcea817c8be13c41 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 12:09:22 -0700 Subject: [PATCH 028/105] reorder _atom_site reader to check column name dictionary for fract/Cartn before reading in coordinates --- src/Crystal.jl | 130 ++++++++++++++++++++++++++----------------------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 8adc995ca..9cb7e914c 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -102,69 +102,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # checking for information about atom sites and symmetry if line[1] == "loop_" next_line = split(lines[i+1], [' ', '\t']; keepempty=false) - if occursin("_atom_site", next_line[1][1:10]) - atom_info = true - atom_column_name = "" - # name_to_column is a dictionary that e.g. returns which column contains x fractional coord - # use example: name_to_column["_atom_site_fract_x"] gives 3 - name_to_column = Dict{AbstractString, Int}() - - i += 1 - loop_starts = i - while length(split(lines[i])) == 1 - if i == loop_starts - atom_column_name = split(lines[i])[1] - end - name_to_column[split(lines[i])[1]] = i + 1 - loop_starts - # iterate to next line in file - i += 1 - end - - # if the file provides fractional coordinates - fractional = haskey(name_to_column, "_atom_site_fract_x") && - haskey(name_to_column, "_atom_site_fract_y") && - haskey(name_to_column, "_atom_site_fract_z") - # if the file provides cartesian coordinates - cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && - haskey(name_to_column, "_atom_site_Cartn_y") && - haskey(name_to_column, "_atom_site_Cartn_z") && - ! fractional # if both are provided, will default - # to using fractional, so keep cartesian - # false - - # read in atom_site info and store it in column based on - # the name_to_column dictionary - while i <= length(lines) && length(split(lines[i])) == length(name_to_column) - line = split(lines[i]) - - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - if fractional - coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] - elseif cartesian - coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), - parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), - parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] - else - error("The file does not store atom information in the form '_atom_site_fract_x' or '_atom_site_Cartn_x'") - end - # If charges present, import them - if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) - else - push!(charges_simple, 0.0) - end - # iterate to next line in file - i += 1 - end - - # finish reading in atom_site information, skip to next - # iteration of outer while-loop - # prevents skipping a line after finishing reading atoms - continue # only read in symmetry if the structure is not in P1 symmetry - elseif occursin("_symmetry_equiv_pos", next_line[1]) && !p1_symmetry + if occursin("_symmetry_equiv_pos", next_line[1]) && !p1_symmetry symmetry_info = true symmetry_column_name = "" # name_to_column is a dictionary that e.g. returns which column contains xyz remapping @@ -209,6 +148,73 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # finish reading in symmetry information, skip to next # iteration of outer while-loop continue + # read in keywords, store in a dictionary, then if + # `_atom_site_fract_.` or `_atom_site_Cartn_.` it will + # proceed to read in atom coordinate information + elseif ! atom_info + atom_column_name = "" + # name_to_column is a dictionary that e.g. returns which column contains x fractional coord + # use example: name_to_column["_atom_site_fract_x"] gives 3 + name_to_column = Dict{AbstractString, Int}() + + i += 1 + loop_starts = i + while length(split(lines[i])) == 1 && split(lines[i])[1][1] == '_' + if i == loop_starts + atom_column_name = split(lines[i])[1] + end + name_to_column[split(lines[i])[1]] = i + 1 - loop_starts + # iterate to next line in file + i += 1 + end + + # if the file provides fractional coordinates + fractional = haskey(name_to_column, "_atom_site_fract_x") && + haskey(name_to_column, "_atom_site_fract_y") && + haskey(name_to_column, "_atom_site_fract_z") + # if the file provides cartesian coordinates + cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && + haskey(name_to_column, "_atom_site_Cartn_y") && + haskey(name_to_column, "_atom_site_Cartn_z") && + ! fractional # if both are provided, will default + # to using fractional, so keep cartesian + # false + if fractional || cartesian + # found the atom_info, so don't need to check for it + # after reading in the information + atom_info = true + # read in atom_site info and store it in column based on + # the name_to_column dictionary + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) + + push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) + if fractional + coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] + elseif cartesian + coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] + else + error("The file does not store atom information in the form '_atom_site_fract_x' or '_atom_site_Cartn_x'") + end + # If charges present, import them + if haskey(name_to_column, "_atom_site_charge") + push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + else + push!(charges_simple, 0.0) + end + # iterate to next line in file + i += 1 + end + + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue + end end end From fe8dc521bfbb48116cbb75bbde692afe8e551702 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 16:12:44 -0700 Subject: [PATCH 029/105] remove KAXQIL files because they were replaced by ORIVOC for a more thorough test of the cif reader --- test/data/crystals/KAXQIL_clean.cif | 72 ------------- test/data/crystals/KAXQIL_clean_P1.cif | 141 ------------------------- 2 files changed, 213 deletions(-) delete mode 100644 test/data/crystals/KAXQIL_clean.cif delete mode 100644 test/data/crystals/KAXQIL_clean_P1.cif diff --git a/test/data/crystals/KAXQIL_clean.cif b/test/data/crystals/KAXQIL_clean.cif deleted file mode 100644 index e539e59fa..000000000 --- a/test/data/crystals/KAXQIL_clean.cif +++ /dev/null @@ -1,72 +0,0 @@ - -####################################################################### -# -# Cambridge Crystallographic Data Centre -# CCDC -# -####################################################################### -# -# If this CIF has been generated from an entry in the Cambridge -# Structural Database, then it will include bibliographic, chemical, -# crystal, experimental, refinement or atomic coordinate data resulting -# from the CCDC's data processing and validation procedures. -# -####################################################################### - -data_casdb -_symmetry_cell_setting monoclinic -_symmetry_space_group_name_H-M 'P 21/n' -_symmetry_Int_Tables_number 14 -_space_group_name_Hall '-P 2yn' -loop_ -_symmetry_equiv_pos_site_id -_symmetry_equiv_pos_as_xyz -1 x,y,z -2 1/2-x,1/2+y,1/2-z -3 -x,-y,-z -4 1/2+x,1/2-y,1/2+z -_cell_length_a 11.8214(3) -_cell_length_b 5.56730(13) -_cell_length_c 22.7603(5) -_cell_angle_alpha 90.00 -_cell_angle_beta 100.356(2) -_cell_angle_gamma 90.00 -_cell_volume 1473.53 -loop_ -_atom_site_label -_atom_site_type_symbol -_atom_site_fract_x -_atom_site_fract_y -_atom_site_fract_z -Ca1 Ca 0.36318(3) 0.24415(5) 0.277126(14) -S1 S 0.60165(3) 0.03637(7) 0.392320(16) -O6 O 0.50690(10) 0.1950(2) 0.36904(5) -O1 O 0.73774(11) 0.0848(2) 0.69064(5) -O5 O 0.58466(12) -0.2189(2) 0.38824(6) -O3 O 1.01577(12) 0.4743(2) 0.25622(6) -O4 O 1.04305(12) 0.0809(2) 0.24715(7) -C4 C 0.99546(14) 0.2586(3) 0.26511(8) -C4A C 0.72326(14) 0.2607(3) 0.65507(7) -C6 C 0.64494(14) 0.1092(3) 0.46864(7) -C7 C 0.71894(14) 0.1104(3) 0.35781(7) -C8 C 0.80704(16) -0.0567(3) 0.36004(8) -H8 H 0.8055 -0.1996 0.3811 -C9 C 0.72975(17) -0.0114(3) 0.56772(8) -H9 H 0.7682 -0.1249 0.5939 -C10 C 0.69872(14) 0.2073(3) 0.58931(7) -C11 C 0.61656(16) 0.3313(3) 0.48920(8) -H11 H 0.5794 0.4459 0.4628 -C12 C 0.72051(15) 0.3272(3) 0.32810(8) -H12 H 0.6612 0.4377 0.3271 -C13 C 0.81276(15) 0.3756(3) 0.29975(8) -H13 H 0.8161 0.5217 0.2803 -C14 C 0.89738(16) -0.0071(3) 0.33040(9) -H14 H 0.9561 -0.1186 0.3309 -C15 C 0.70345(18) -0.0610(3) 0.50695(8) -H15 H 0.7249 -0.2066 0.4922 -C16 C 0.90018(14) 0.2083(3) 0.30006(7) -C17 C 0.64454(16) 0.3796(3) 0.54979(8) -H17 H 0.6269 0.5287 0.5642 -O2 O 0.73242(11) 0.4749(2) 0.67168(6) - -#END diff --git a/test/data/crystals/KAXQIL_clean_P1.cif b/test/data/crystals/KAXQIL_clean_P1.cif deleted file mode 100644 index dbd81888a..000000000 --- a/test/data/crystals/KAXQIL_clean_P1.cif +++ /dev/null @@ -1,141 +0,0 @@ -# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net -data_I -_chemical_name_common 'casdb' -_cell_length_a 11.8214 -_cell_length_b 5.5673 -_cell_length_c 22.7603 -_cell_angle_alpha 90 -_cell_angle_beta 100.356 -_cell_angle_gamma 90 -_space_group_name_H-M_alt 'P 1' -_space_group_name_Hall 'P 1' -loop_ - _symmetry_equiv_pos_as_xyz - x,y,z -loop_ - _atom_site_label - _atom_site_type_symbol - _atom_site_fract_x - _atom_site_fract_y - _atom_site_fract_z - _atom_site_occupancy - Ca1 Ca 0.36318 0.24415 0.27713 1.000 - S1 S 0.60165 0.03637 0.39232 1.000 - O6 O 0.50690 0.19500 0.36904 1.000 - O1 O 0.73774 0.08480 0.69064 1.000 - O5 O 0.58466 0.78110 0.38824 1.000 - O3 O 0.01577 0.47430 0.25622 1.000 - O4 O 0.04305 0.08090 0.24715 1.000 - C4 C 0.99546 0.25860 0.26511 1.000 - C4A C 0.72326 0.26070 0.65507 1.000 - C6 C 0.64494 0.10920 0.46864 1.000 - C7 C 0.71894 0.11040 0.35781 1.000 - C8 C 0.80704 0.94330 0.36004 1.000 - H8 H 0.80550 0.80040 0.38110 1.000 - C9 C 0.72975 0.98860 0.56772 1.000 - H9 H 0.76820 0.87510 0.59390 1.000 - C10 C 0.69872 0.20730 0.58931 1.000 - C11 C 0.61656 0.33130 0.48920 1.000 - H11 H 0.57940 0.44590 0.46280 1.000 - C12 C 0.72051 0.32720 0.32810 1.000 - H12 H 0.66120 0.43770 0.32710 1.000 - C13 C 0.81276 0.37560 0.29975 1.000 - H13 H 0.81610 0.52170 0.28030 1.000 - C14 C 0.89738 0.99290 0.33040 1.000 - H14 H 0.95610 0.88140 0.33090 1.000 - C15 C 0.70345 0.93900 0.50695 1.000 - H15 H 0.72490 0.79340 0.49220 1.000 - C16 C 0.90018 0.20830 0.30006 1.000 - C17 C 0.64454 0.37960 0.54979 1.000 - H17 H 0.62690 0.52870 0.56420 1.000 - O2 O 0.73242 0.47490 0.67168 1.000 - Ca1 Ca 0.13682 0.74415 0.22287 1.000 - Ca1 Ca 0.63682 0.75585 0.72287 1.000 - Ca1 Ca 0.86318 0.25585 0.77713 1.000 - S1 S 0.89835 0.53637 0.10768 1.000 - S1 S 0.39835 0.96363 0.60768 1.000 - S1 S 0.10165 0.46363 0.89232 1.000 - O6 O 0.99310 0.69500 0.13096 1.000 - O6 O 0.49310 0.80500 0.63096 1.000 - O6 O 0.00690 0.30500 0.86904 1.000 - O1 O 0.76226 0.58480 0.80936 1.000 - O1 O 0.26226 0.91520 0.30936 1.000 - O1 O 0.23774 0.41520 0.19064 1.000 - O5 O 0.91534 0.28110 0.11176 1.000 - O5 O 0.41534 0.21890 0.61176 1.000 - O5 O 0.08466 0.71890 0.88824 1.000 - O3 O 0.48423 0.97430 0.24378 1.000 - O3 O 0.98423 0.52570 0.74378 1.000 - O3 O 0.51577 0.02570 0.75622 1.000 - O4 O 0.45695 0.58090 0.25285 1.000 - O4 O 0.95695 0.91910 0.75285 1.000 - O4 O 0.54305 0.41910 0.74715 1.000 - C4 C 0.50454 0.75860 0.23489 1.000 - C4 C 0.00454 0.74140 0.73489 1.000 - C4 C 0.49546 0.24140 0.76511 1.000 - C4A C 0.77674 0.76070 0.84493 1.000 - C4A C 0.27674 0.73930 0.34493 1.000 - C4A C 0.22326 0.23930 0.15507 1.000 - C6 C 0.85506 0.60920 0.03136 1.000 - C6 C 0.35506 0.89080 0.53136 1.000 - C6 C 0.14494 0.39080 0.96864 1.000 - C7 C 0.78106 0.61040 0.14219 1.000 - C7 C 0.28106 0.88960 0.64219 1.000 - C7 C 0.21894 0.38960 0.85781 1.000 - C8 C 0.69296 0.44330 0.13996 1.000 - C8 C 0.19296 0.05670 0.63996 1.000 - C8 C 0.30704 0.55670 0.86004 1.000 - H8 H 0.69450 0.30040 0.11890 1.000 - H8 H 0.19450 0.19960 0.61890 1.000 - H8 H 0.30550 0.69960 0.88110 1.000 - C9 C 0.77025 0.48860 0.93228 1.000 - C9 C 0.27025 0.01140 0.43228 1.000 - C9 C 0.22975 0.51140 0.06772 1.000 - H9 H 0.73180 0.37510 0.90610 1.000 - H9 H 0.23180 0.12490 0.40610 1.000 - H9 H 0.26820 0.62490 0.09390 1.000 - C10 C 0.80128 0.70730 0.91069 1.000 - C10 C 0.30128 0.79270 0.41069 1.000 - C10 C 0.19872 0.29270 0.08931 1.000 - C11 C 0.88344 0.83130 0.01080 1.000 - C11 C 0.38344 0.66870 0.51080 1.000 - C11 C 0.11656 0.16870 0.98920 1.000 - H11 H 0.92060 0.94590 0.03720 1.000 - H11 H 0.42060 0.55410 0.53720 1.000 - H11 H 0.07940 0.05410 0.96280 1.000 - C12 C 0.77949 0.82720 0.17190 1.000 - C12 C 0.27949 0.67280 0.67190 1.000 - C12 C 0.22051 0.17280 0.82810 1.000 - H12 H 0.83880 0.93770 0.17290 1.000 - H12 H 0.33880 0.56230 0.67290 1.000 - H12 H 0.16120 0.06230 0.82710 1.000 - C13 C 0.68724 0.87560 0.20025 1.000 - C13 C 0.18724 0.62440 0.70025 1.000 - C13 C 0.31276 0.12440 0.79975 1.000 - H13 H 0.68390 0.02170 0.21970 1.000 - H13 H 0.18390 0.47830 0.71970 1.000 - H13 H 0.31610 0.97830 0.78030 1.000 - C14 C 0.60262 0.49290 0.16960 1.000 - C14 C 0.10262 0.00710 0.66960 1.000 - C14 C 0.39738 0.50710 0.83040 1.000 - H14 H 0.54390 0.38140 0.16910 1.000 - H14 H 0.04390 0.11860 0.66910 1.000 - H14 H 0.45610 0.61860 0.83090 1.000 - C15 C 0.79655 0.43900 0.99305 1.000 - C15 C 0.29655 0.06100 0.49305 1.000 - C15 C 0.20345 0.56100 0.00695 1.000 - H15 H 0.77510 0.29340 0.00780 1.000 - H15 H 0.27510 0.20660 0.50780 1.000 - H15 H 0.22490 0.70660 0.99220 1.000 - C16 C 0.59982 0.70830 0.19994 1.000 - C16 C 0.09982 0.79170 0.69994 1.000 - C16 C 0.40018 0.29170 0.80006 1.000 - C17 C 0.85546 0.87960 0.95021 1.000 - C17 C 0.35546 0.62040 0.45021 1.000 - C17 C 0.14454 0.12040 0.04979 1.000 - H17 H 0.87310 0.02870 0.93580 1.000 - H17 H 0.37310 0.47130 0.43580 1.000 - H17 H 0.12690 0.97130 0.06420 1.000 - O2 O 0.76758 0.97490 0.82832 1.000 - O2 O 0.26758 0.52510 0.32832 1.000 - O2 O 0.23242 0.02510 0.17168 1.000 From b48d79cdbd91ea8e0a7b57390aa4d9ae5405defe Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 5 Aug 2019 16:52:08 -0700 Subject: [PATCH 030/105] create infinite framework addition with keyword argument for checking for overlap --- src/Crystal.jl | 38 +++++++++++++++++++++++--------------- test/crystal_test.jl | 6 ++++++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9cb7e914c..7b0d10cce 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1005,23 +1005,31 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) return box_flag && charges_flag && atoms_flag && symmetry_flag end -# TODO do something about symmetry rules, force to have same -function Base.:+(f1::Framework, f2::Framework) - @assert isapprox(f1.box, f2.box) "Two frameworks need the same Box to allow addition" - @assert is_symmetry_equal(f1.symmetry, f2.symmetry) "Symmetry rules are different" - - new_atoms = f1.atoms + f2.atoms - new_charges = f1.charges + f2.charges - - new_framework = Framework(f1.name * "_" * f2.name, f1.box, new_atoms, new_charges, f1.symmetry, f1.is_p1) - - if atom_overlap(new_framework) - @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" +function Base.:+(frameworks::Framework...; check_overlap=true) + new_framework = Framework("", frameworks[1].box, + Atoms(Array{Symbol, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), + Charges(Array{Float64, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), + frameworks[1].symmetry, frameworks[1].is_p1) + for f in frameworks + @assert isapprox(new_framework.box, f.box) "All Frameworks need same unit box to be combined" + @assert is_symmetry_equal(new_framework.symmetry, f.symmetry) "All Frameworks need the same symmetry to be combined" + + new_atoms = new_framework.atoms + f.atoms + new_charges = new_framework.charges + f.charges + + new_framework = Framework(new_framework.name * "_" * f.name, new_framework.box, + new_atoms, new_charges, new_framework.symmetry, + new_framework.is_p1) end + if check_overlap + if atom_overlap(new_framework) + @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" + end - if charge_overlap(new_framework) - @warn "This new framework has overlapping charges, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" + if charge_overlap(new_framework) + @warn "This new framework has overlapping charges, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" + end end - + return new_framework end diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 4c389949b..b256a5521 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -161,6 +161,12 @@ using Random @test isapprox(f1.atoms + f2.atoms, f3.atoms) @test isapprox(f1.charges + f2.charges, f3.charges) + # test infinite framework_addition + f4 = +(f1, f2, f3; check_overlap=false) + @test isapprox(f3.box, f4.box) + @test isapprox(f4.atoms, f1.atoms + f2.atoms + f3.atoms) + @test isapprox(f4.charges, f1.charges + f2.charges + f3.charges) + # more xtal tests sbmof1 = Framework("SBMOF-1.cif") @test !charged(sbmof1) From c3ef18c5491d371f49d884972063ccaac546c435 Mon Sep 17 00:00:00 2001 From: ahyork <33434154+ahyork@users.noreply.github.com> Date: Tue, 6 Aug 2019 13:46:35 -0700 Subject: [PATCH 031/105] Update Crystal.jl add documentation for framework changes and changed f -> framework in new functions to maintain consistency --- src/Crystal.jl | 63 +++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 7b0d10cce..fc0738286 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -25,6 +25,8 @@ or construct a `Framework` data structure directly. - `net_charge_tol::Float64`: when checking for charge neutrality, throw an error if the absolute value of the net charge is larger than this value. - `check_atom_and_charge_overlap::Bool`: throw an error if overlapping atoms are detected. - `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. +- `convert_to_p1::Bool`: If the structure is not in P1 it will be converted to + P1 symmetry using the symmetry rules # Returns - `framework::Framework`: A framework containing the crystal structure information @@ -34,6 +36,11 @@ or construct a `Framework` data structure directly. - `box::Box`: unit cell (Bravais Lattice) - `atoms::Atoms`: list of Atoms in crystal unit cell - `charges::Charges`: list of point charges in crystal unit cell +- `symmetry::Array{Function, 2}`: 2D array of anonymous functions that represent + the symmetry operations. If the structure is in P1 there will be one + symmetry operation. +- `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This + is used before any simulations such as GCMC and Henry Coefficient """ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, @@ -119,7 +126,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end @assert haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") "Need column name `_symmetry_equiv_pos_xyz` to parse symmetry information" - + symmetry_count = 0 # CSD stores symmetry as one column in a string that ends # up getting split on the spaces between commas (i.e. its @@ -144,7 +151,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end @assert symmetry_count == size(symmetry_rules, 2) "number of symmetry rules must match the count" - + # finish reading in symmetry information, skip to next # iteration of outer while-loop continue @@ -167,7 +174,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # iterate to next line in file i += 1 end - + # if the file provides fractional coordinates fractional = haskey(name_to_column, "_atom_site_fract_x") && haskey(name_to_column, "_atom_site_fract_y") && @@ -432,7 +439,7 @@ function write_xyz(framework::Framework, filename::AbstractString; write_xyz(atoms, x, filename, comment=comment) end write_xyz(framework::Framework; comment::AbstractString="", center::Bool=false) = write_xyz( - framework, + framework, replace(replace(framework.name, ".cif" => ""), ".cssr" => "") * ".xyz", comment=comment, center=center) @@ -734,12 +741,16 @@ return the new framework. # Arguments - `f::Framework`: The framework to be converted to P1 symmetry +- `check_charge_neutrality::Bool`: check for charge neutrality +- `net_charge_tol::Float64`: when checking for charge neutrality, throw an error if the absolute value of the net charge is larger than this value. +- `check_atom_and_charge_overlap::Bool`: throw an error if overlapping atoms are detected. +- `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. # Returns - `P1_framework::Framework`: The framework after it has been converted to P1 symmetry. The new symmetry rules will be the P1 symemtry rules """ -function apply_symmetry_rules(f::Framework; check_charge_neutrality::Bool=true, +function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, remove_overlap::Bool=false) new_atom_xfs = Array{Float64, 2}(undef, 3, 0) @@ -748,53 +759,57 @@ function apply_symmetry_rules(f::Framework; check_charge_neutrality::Bool=true, new_charge_qs = Array{Float64, 1}(undef, 0) # for each symmetry rule - for i in 1:size(f.symmetry, 2) + for i in 1:size(framework.symmetry, 2) # loop over all atoms in lower level symemtry - for j in 1:size(f.atoms.xf, 2) + for j in 1:size(framework.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_atom_xfs = [new_atom_xfs [Base.invokelatest.(f.symmetry[k, i], f.atoms.xf[:, j]...) for k in 1:3]] + new_atom_xfs = [new_atom_xfs [Base.invokelatest.( + framework.symmetry[k, i], framework.atoms.xf[:, j]...) for k in 1:3]] end # loop over all charges in lower level symmetry - for j in 1:size(f.charges.xf, 2) + for j in 1:size(framework.charges.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_charge_xfs = [new_charge_xfs [Base.invokelatest.(f.symmetry[k, i], f.charges.xf[:, j]...) for k in 1:3]] + new_charge_xfs = [new_charge_xfs [Base.invokelatest.( + framework.symmetry[k, i], framework.charges.xf[:, j]...) for k in 1:3]] end # repeat charge_qs and atom_species for every symmetry applied - new_atom_species = [new_atom_species; f.atoms.species] - new_charge_qs = [new_charge_qs; f.charges.q] + new_atom_species = [new_atom_species; framework.atoms.species] + new_charge_qs = [new_charge_qs; framework.charges.q] end new_symmetry_rules = [Array{Float64, 2}(undef, 3, 0) [(x, y, z) -> x, (x, y, z) -> y, (x, y, z) -> z]] - new_f = Framework(f.name, f.box, Atoms(new_atom_species, new_atom_xfs), Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, true) + new_framework = Framework(framework.name, framework.box, + Atoms(new_atom_species, new_atom_xfs), + Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, true) if check_charge_neutrality - if ! charge_neutral(new_f, net_charge_tol) + if ! charge_neutral(new_framework, net_charge_tol) error(@sprintf("Framework %s is not charge neutral; net charge is %f e. Ignore this error message by passing check_charge_neutrality=false or increasing the net charge tolerance `net_charge_tol`\n", - new_f.name, total_charge(new_f))) + new_framework.name, total_charge(new_framework))) end end if remove_overlap - return remove_overlapping_atoms_and_charges(new_f) + return remove_overlapping_atoms_and_charges(new_framework) end if check_atom_and_charge_overlap - if atom_overlap(new_f) | charge_overlap(new_f) + if atom_overlap(new_framework) | charge_overlap(new_framework) error(@sprintf("At least one pair of atoms/charges overlap in %s. - Consider passing `remove_overlap=true`\n", new_f.name)) + Consider passing `remove_overlap=true`\n", new_framework.name)) end end - return new_f + return new_framework end """ - symmetry_equal = is_symmetry_equal(sym1, sym2) + symmetry_equal = is_symmetry_equal(framework1.symmetry, framework2.symmetry) Returns true if both symmetry rules can create the same set from the same set of coordinates. Returns false if they don't contain the same number of rules or @@ -891,8 +906,8 @@ function write_cif(framework::Framework, filename::AbstractString) error("write_cif assumes charges correspond to LJspheres") end end - @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], - framework.atoms.xf[1, i], framework.atoms.xf[2, i], + @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], + framework.atoms.xf[1, i], framework.atoms.xf[2, i], framework.atoms.xf[3, i], q) end close(cif_file) @@ -1016,13 +1031,13 @@ function Base.:+(frameworks::Framework...; check_overlap=true) new_atoms = new_framework.atoms + f.atoms new_charges = new_framework.charges + f.charges - + new_framework = Framework(new_framework.name * "_" * f.name, new_framework.box, new_atoms, new_charges, new_framework.symmetry, new_framework.is_p1) end if check_overlap - if atom_overlap(new_framework) + if atom_overlap(new_framework) @warn "This new framework has overlapping atoms, use:\n`remove_overlapping_atoms_and_charges(framework)`\nto remove them" end From 1be421046a3ded2a6e9eab84287b65b6491d47aa Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 7 Aug 2019 13:51:52 -0700 Subject: [PATCH 032/105] make assertion errors in framework addition provide some information --- src/Crystal.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index fc0738286..875fc68f6 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1026,8 +1026,8 @@ function Base.:+(frameworks::Framework...; check_overlap=true) Charges(Array{Float64, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), frameworks[1].symmetry, frameworks[1].is_p1) for f in frameworks - @assert isapprox(new_framework.box, f.box) "All Frameworks need same unit box to be combined" - @assert is_symmetry_equal(new_framework.symmetry, f.symmetry) "All Frameworks need the same symmetry to be combined" + @assert isapprox(new_framework.box, f.box) @sprintf("Framework %s has a different box\n", f.name) + @assert is_symmetry_equal(new_framework.symmetry, f.symmetry) @sprintf("Framework %s has different symmetry rules\n", f.name) new_atoms = new_framework.atoms + f.atoms new_charges = new_framework.charges + f.charges From 76bd21b9390568994a9e5ddaa506ad02294bc537 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 7 Aug 2019 16:13:11 -0700 Subject: [PATCH 033/105] add option to write out cifs in cartesian coordinates --- src/Crystal.jl | 19 ++++++++++++++----- test/crystal_test.jl | 3 +++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 875fc68f6..430ff7311 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -864,7 +864,7 @@ end Write a `framework::Framework` to a .cif file with `filename::AbstractString`. If `filename` does not include the .cif extension, it will automatically be added. """ -function write_cif(framework::Framework, filename::AbstractString) +function write_cif(framework::Framework, filename::AbstractString; fractional::Bool=true) if charged(framework) && (framework.atoms.n_atoms != framework.charges.n_charges) error("write_cif assumes equal numbers of Charges and Atoms (or zero charges)") end @@ -895,7 +895,11 @@ function write_cif(framework::Framework, filename::AbstractString) @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n 'x, y, z'\n\n") @printf(cif_file, "loop_\n_atom_site_label\n") - @printf(cif_file, "_atom_site_fract_x\n_atom_site_fract_y\n_atom_site_fract_z\n") + if fractional + @printf(cif_file, "_atom_site_fract_x\n_atom_site_fract_y\n_atom_site_fract_z\n") + else + @printf(cif_file, "_atom_site_Cartn_x\n_atom_site_Cartn_y\n_atom_site_Cartn_z\n") + end @printf(cif_file, "_atom_site_charge\n") for i = 1:framework.atoms.n_atoms @@ -906,9 +910,14 @@ function write_cif(framework::Framework, filename::AbstractString) error("write_cif assumes charges correspond to LJspheres") end end - @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], - framework.atoms.xf[1, i], framework.atoms.xf[2, i], - framework.atoms.xf[3, i], q) + if fractional + @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], + framework.atoms.xf[:, i]..., q) + else + + @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], + (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) + end end close(cif_file) end diff --git a/test/crystal_test.jl b/test/crystal_test.jl index b256a5521..4c9ad3c15 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -31,7 +31,10 @@ using Random # test .cif writer; write, read in, assert equal write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2.cif")) framework_rewritten = Framework("rewritten_test_structure2.cif") + write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2_cartn.cif"); fractional=false) + framework_rewritten_cartn = Framework("rewritten_test_structure2_cartn.cif") @test isapprox(framework, framework_rewritten) + @test isapprox(framework, framework_rewritten_cartn) # test .cif reader for non-P1 symmetry # no atoms should overlap From 0eaa9921fbb9668596dff7ee4d5ca2fe403da1b7 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 8 Aug 2019 13:11:44 -0700 Subject: [PATCH 034/105] read in symmetry group and change symmetry to be in string format, converted when needed. change back to '_symmetry_space_group_name_H-M' and convert the extended to regular in the orivoc files --- src/Crystal.jl | 91 +++++++++++++---------- test/crystal_test.jl | 29 +++++--- test/data/crystals/ORIVOC_clean.cif | 2 +- test/data/crystals/ORIVOC_clean_fract.cif | 2 +- 4 files changed, 70 insertions(+), 54 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 430ff7311..47ee1aa8b 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -6,7 +6,8 @@ struct Framework box::Box atoms::Atoms charges::Charges - symmetry::Array{Function, 2} + symmetry::Array{AbstractString, 2} + space_group::AbstractString is_p1::Bool end @@ -14,7 +15,7 @@ end framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, remove_overlap=false) - framework = Framework(name, box, atoms, charges) + framework = Framework(name, box, atoms, charges, symmetry, space_group, is_p1) Read a crystal structure file (.cif or .cssr) and populate a `Framework` data structure, or construct a `Framework` data structure directly. @@ -65,13 +66,14 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, coords = Array{Float64, 2}(undef, 3, 0) # default for symmetry rules is P1. # These will be overwritten if the user chooses to read in non-P1 - symmetry_rules = Array{Function, 2}(undef, 3, 0) + symmetry_rules = Array{AbstractString, 2}(undef, 3, 0) # used for remembering whether fractional/cartesian coordinates are read in # placed here so it will be defined for the if-stmt after the box is defined fractional = false cartesian = false # used for determining if the framework is in P1 symmetry for simulations p1_symmetry = false + space_group = "" # Start of .cif reader @@ -94,15 +96,17 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end # Make sure the space group is P1 - if line[1] == "_symmetry_space_group_name_H-M" || line[1] == "_space_group_name_H-M_alt" - if length(line) == 3 - p1_symmetry = (occursin("P1", line[2] * line[3]) || - occursin("P 1", line[2] * line[3]) || - occursin("-P1", line[2] * line[3])) - elseif length(line) == 2 - p1_symmetry = (occursin("P1", line[2]) || - occursin("P 1", line[2]) || - occursin("-P1", line[2])) + if line[1] == "_symmetry_space_group_name_H-M" + # use anonymous function to combine all terms past the first + # to extract space group name + space_group = reduce((x, y) -> x * " " * y, line[2:end]) + space_group = split(space_group, ''', keepempty=false)[1] + @printf("Framework %s has %s space group\n", filename, space_group) + if space_group == "P1" || space_group == "P 1" || + space_group == "-P1" + # simplify by only having one P1 space_group name + space_group = "P1" + p1_symmetry = true end end @@ -138,11 +142,12 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, line = lines[i] sym_funcs = split(line, [' ', ',', '''], keepempty=false) - new_sym_rule = Array{Function, 1}(undef, 3) + # store as strings so it can be written out later + new_sym_rule = Array{AbstractString, 1}(undef, 3) sym_start = name_to_column["_symmetry_equiv_pos_as_xyz"] - 1 for j = 1:3 - new_sym_rule[j] = eval(Meta.parse("(x, y, z) -> " * sym_funcs[j + sym_start])) + new_sym_rule[j] = sym_funcs[j + sym_start] end symmetry_rules = [symmetry_rules new_sym_rule] @@ -276,7 +281,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, new_col = Array{Float64, 1}(undef, 0) # loop over all atom positions from lower level symmetry for j in 1:size(coords_simple, 2) - coords = [coords [Base.invokelatest(symmetry_rules[k, i], coords_simple[:, j]...) for k in 1:3]] + coords = [coords [Base.invokelatest(eval(Meta.parse("(x, y, z) -> " * symmetry_rules[k, i])), coords_simple[:, j]...) for k in 1:3]] end charge_values = [charge_values; charges_simple] species = [species; species_simple] @@ -289,9 +294,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # either read in P1 or converted to P1 so should have same symmetry rules if p1_symmetry || convert_to_p1 - symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z]] + symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] end # if structure was stored in P1 or converted to P1, store that information for later @@ -330,10 +333,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end # add P1 symmetry rules for consistency - symmetry_rules = [symmetry_rules [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z]] + symmetry_rules = [symmetry_rules ["x", "y", "z"]] p1_symmetry = true + space_group = "P1" end # Construct the unit cell box @@ -344,7 +346,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, idx_nz = charge_values .!= 0.0 charges = Charges(charge_values[idx_nz], coords[:, idx_nz]) - framework = Framework(filename, box, atoms, charges, symmetry_rules, p1_symmetry) + framework = Framework(filename, box, atoms, charges, symmetry_rules, space_group, p1_symmetry) if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) @@ -418,7 +420,7 @@ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) @assert (new_charges.n_charges == framework.charges.n_charges * prod(repfactors)) @assert (new_atoms.n_atoms == framework.atoms.n_atoms * prod(repfactors)) - return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry), framework.is_p1) + return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) end # doc string in Misc.jl @@ -593,7 +595,7 @@ function remove_overlapping_atoms_and_charges(framework::Framework; atoms = Atoms(framework.atoms.species[atoms_to_keep], atom_coords_to_keep) charges = Charges(framework.charges.q[charges_to_keep], charge_coords_to_keep) - new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry), framework.is_p1) + new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) @assert (! atom_overlap(new_framework, overlap_tol=atom_overlap_tol)) @assert (! charge_overlap(new_framework, overlap_tol=charge_overlap_tol)) @@ -734,7 +736,7 @@ function crystal_density(framework::Framework) end """ - simulation_ready_framework = apply_symemtry_rules(non_p1_framework) + simulation_ready_framework = apply_symmetry_rules(non_p1_framework) Convert a framework to P1 symmetry based on internal symmetry rules. This will return the new framework. @@ -764,26 +766,26 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo for j in 1:size(framework.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates new_atom_xfs = [new_atom_xfs [Base.invokelatest.( - framework.symmetry[k, i], framework.atoms.xf[:, j]...) for k in 1:3]] + eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), + framework.atoms.xf[:, j]...) for k in 1:3]] end # loop over all charges in lower level symmetry for j in 1:size(framework.charges.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates new_charge_xfs = [new_charge_xfs [Base.invokelatest.( - framework.symmetry[k, i], framework.charges.xf[:, j]...) for k in 1:3]] + eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), + framework.charges.xf[:, j]...) for k in 1:3]] end # repeat charge_qs and atom_species for every symmetry applied new_atom_species = [new_atom_species; framework.atoms.species] new_charge_qs = [new_charge_qs; framework.charges.q] end - new_symmetry_rules = [Array{Float64, 2}(undef, 3, 0) [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z]] + new_symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] new_framework = Framework(framework.name, framework.box, Atoms(new_atom_species, new_atom_xfs), - Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, true) + Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, "P1", true) if check_charge_neutrality if ! charge_neutral(new_framework, net_charge_tol) @@ -816,16 +818,16 @@ of coordinates. Returns false if they don't contain the same number of rules or if they create different sets of points. # Arguments -- `sym1::Array{Function, 2}`: Array of anonymous functions that represent +- `sym1::Array{AbstractString, 2}`: Array of strings that represent symmetry operations -- `sym2::Array{Function, 2}`: Array of anonymous functions that represent +- `sym2::Array{AbstractString, 2}`: Array of strings that represent symmetry operations # Returns - `is_equal::Bool`: True if they are the same set of symmetry rules False if they are different """ -function is_symmetry_equal(sym1::Array{Function, 2}, sym2::Array{Function, 2}) +function is_symmetry_equal(sym1::Array{AbstractString, 2}, sym2::Array{AbstractString, 2}) # need same number of symmetry operations if size(sym1, 2) != size(sym2, 2) return false @@ -842,11 +844,13 @@ function is_symmetry_equal(sym1::Array{Function, 2}, sym2::Array{Function, 2}) for i in 1:size(test_array, 2) # loop over f1 symmetry rules for j in 1:size(sym1, 2) - sym1_applied_to_test = [sym1_applied_to_test [Base.invokelatest.(sym1[k, j], test_array[:, i]...) for k in 1:3]] + sym1_applied_to_test = [sym1_applied_to_test [Base.invokelatest.( + eval(Meta.parse("(x, y, z) -> " * sym1[k, j])), test_array[:, i]...) for k in 1:3]] end # loop over f2 symmetry rules for j in 1:size(sym2, 2) - sym2_applied_to_test = [sym2_applied_to_test [Base.invokelatest.(sym2[k, j], test_array[:, i]...) for k in 1:3]] + sym2_applied_to_test = [sym2_applied_to_test [Base.invokelatest.( + eval(Meta.parse("(x, y, z) -> " * sym2[k, j])), test_array[:, i]...) for k in 1:3]] end end @@ -881,7 +885,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B @printf(cif_file, "data_%s_PM\n", split(framework.name, ".")[1]) end - @printf(cif_file, "_symmetry_space_group_name_H-M 'P 1'\n") + @printf(cif_file, "_symmetry_space_group_name_H-M '%s'\n", framework.space_group) @printf(cif_file, "_cell_length_a %f\n", framework.box.a) @printf(cif_file, "_cell_length_b %f\n", framework.box.b) @@ -892,7 +896,11 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B @printf(cif_file, "_cell_angle_gamma %f\n", framework.box.γ * 180.0 / pi) @printf(cif_file, "_symmetry_Int_Tables_number 1\n\n") - @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n 'x, y, z'\n\n") + @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n") + for i in 1:size(framework.symmetry, 2) + @printf(cif_file, "'%s,%s,%s'\n", framework.symmetry[:, i]...) + end + @printf(cif_file, "\n") @printf(cif_file, "loop_\n_atom_site_label\n") if fractional @@ -990,7 +998,7 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 charges = Charges(charge_vals, charge_coords) # construct new framework - new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry), framework.is_p1) + new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) # check for charge neutrality if abs(total_charge(new_framework)) > net_charge_tol @@ -1033,17 +1041,18 @@ function Base.:+(frameworks::Framework...; check_overlap=true) new_framework = Framework("", frameworks[1].box, Atoms(Array{Symbol, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), Charges(Array{Float64, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), - frameworks[1].symmetry, frameworks[1].is_p1) + frameworks[1].symmetry, frameworks[1].space_group, frameworks[1].is_p1) for f in frameworks @assert isapprox(new_framework.box, f.box) @sprintf("Framework %s has a different box\n", f.name) @assert is_symmetry_equal(new_framework.symmetry, f.symmetry) @sprintf("Framework %s has different symmetry rules\n", f.name) + @assert new_framework.space_group == f.space_group new_atoms = new_framework.atoms + f.atoms new_charges = new_framework.charges + f.charges new_framework = Framework(new_framework.name * "_" * f.name, new_framework.box, new_atoms, new_charges, new_framework.symmetry, - new_framework.is_p1) + new_framework.space_group, new_framework.is_p1) end if check_overlap if atom_overlap(new_framework) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 4c9ad3c15..a4ed8fd39 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -29,6 +29,7 @@ using Random @test isapprox(framework.atoms, framework2.atoms) && isapprox(framework.charges, framework2.charges) # test .cif writer; write, read in, assert equal + # more write_cif tests below with symmetry tests write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2.cif")) framework_rewritten = Framework("rewritten_test_structure2.cif") write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2_cartn.cif"); fractional=false) @@ -78,6 +79,16 @@ using Random @test ! non_P1_framework_symmetry.is_p1 @test ! non_P1_cartesian_symmetry.is_p1 + # test write_cif in non_p1 symmetry + write_cif(non_P1_framework_symmetry, joinpath("data", "crystals", "rewritten_ORIVOC_clean_fract.cif")) + # keep this in cartesian to test both + write_cif(non_P1_cartesian_symmetry, joinpath("data", "crystals", "rewritten_ORIVOC_clean.cif"), fractional=false) + rewritten_non_p1_fractional = Framework("rewritten_ORIVOC_clean_fract.cif"; convert_to_p1=false) + rewritten_non_p1_cartesian = Framework("rewritten_ORIVOC_clean.cif"; convert_to_p1=false) + + @test isapprox(rewritten_non_p1_fractional, non_P1_framework_symmetry) + @test isapprox(rewritten_non_p1_cartesian, non_P1_cartesian_symmetry) + non_P1_framework_symmetry = apply_symmetry_rules(non_P1_framework_symmetry, remove_overlap=true) non_P1_cartesian_symmetry = apply_symmetry_rules(non_P1_cartesian_symmetry, remove_overlap=true) @@ -117,15 +128,11 @@ using Random # test symmetry_rules # define default symmetry_rules - symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> x, - (x, y, z) -> y, - (x, y, z) -> z]] - other_symmetry_rules = [Array{Function, 2}(undef, 3, 0) [(x, y, z) -> y + z, - (x, y, z) -> x + z, - (x, y, z) -> x + y]] - symmetry_rules_two = [(x, y, z) -> x (x, y, z) -> y + z; - (x, y, z) -> y (x, y, z) -> x + z; - (x, y, z) -> z (x, y, z) -> x + y] + symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] + other_symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["y + z", "x + z", "x + y"]] + symmetry_rules_two = [Array{AbstractString, 2}(undef, 3, 0) ["x" "y + z"; + "y" "x + z"; + "z" "x + y"]] symmetry_rules_two_cpy = deepcopy(symmetry_rules_two) @test ! is_symmetry_equal(symmetry_rules, symmetry_rules_two) @test ! is_symmetry_equal(symmetry_rules, other_symmetry_rules) @@ -144,7 +151,7 @@ using Random 2.0 5.0; 3.0 6.0]), deepcopy(symmetry_rules), - true) + "P1", true) f2 = Framework("framework 2", UnitCube(), Atoms( [:c, :d], [7.0 10.0; @@ -156,7 +163,7 @@ using Random 8.0 11.0; 9.0 12.0]), deepcopy(symmetry_rules), - true) + "P1", true) f3 = f1 + f2 @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) diff --git a/test/data/crystals/ORIVOC_clean.cif b/test/data/crystals/ORIVOC_clean.cif index 85d93af75..f1cf35577 100644 --- a/test/data/crystals/ORIVOC_clean.cif +++ b/test/data/crystals/ORIVOC_clean.cif @@ -7,7 +7,7 @@ _cell_length_c 6.65197 _cell_angle_alpha 90 _cell_angle_beta 90 _cell_angle_gamma 120 -_space_group_name_H-M_alt 'R -3' +_symmetry_space_group_name_H-M 'R -3' _space_group_name_Hall '-R 3' loop_ _symmetry_equiv_pos_as_xyz diff --git a/test/data/crystals/ORIVOC_clean_fract.cif b/test/data/crystals/ORIVOC_clean_fract.cif index b1ab2bd2a..1851566ea 100644 --- a/test/data/crystals/ORIVOC_clean_fract.cif +++ b/test/data/crystals/ORIVOC_clean_fract.cif @@ -7,7 +7,7 @@ _cell_length_c 6.65197 _cell_angle_alpha 90 _cell_angle_beta 90 _cell_angle_gamma 120 -_space_group_name_H-M_alt 'R -3' +_symmetry_space_group_name_H-M 'R -3' _space_group_name_Hall '-R 3' loop_ _symmetry_equiv_pos_as_xyz From 3202e30c6ec5cbff0a522f698e0ad8e9154175a0 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 8 Aug 2019 13:26:04 -0700 Subject: [PATCH 035/105] remove debugging line, add information to the show operation for frameworks --- src/Crystal.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 47ee1aa8b..7b591664f 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -101,7 +101,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # to extract space group name space_group = reduce((x, y) -> x * " " * y, line[2:end]) space_group = split(space_group, ''', keepempty=false)[1] - @printf("Framework %s has %s space group\n", filename, space_group) if space_group == "P1" || space_group == "P 1" || space_group == "-P1" # simplify by only having one P1 space_group name @@ -1016,6 +1015,11 @@ function Base.show(io::IO, framework::Framework) @printf(io, "Number of atoms = %d\n", framework.atoms.n_atoms) @printf(io, "Number of charges = %d\n", framework.charges.n_charges) println(io, "Chemical formula: ", chemical_formula(framework)) + @printf(io, "Space Group: %s\n", framework.space_group) + @printf(io, "Symmetry Operations:\n") + for i in 1:size(framework.symmetry, 2) + @printf(io, "\t'%s, %s, %s'\n", framework.symmetry[:, i]...) + end end # TODO add something comparing symmetry rules From 916643755b6d1e4b952fb384f1c0ec67b355cf0d Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 8 Aug 2019 13:58:43 -0700 Subject: [PATCH 036/105] add second quote type for removal when checking space group --- src/Crystal.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 7b591664f..9b8062b83 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -100,7 +100,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # use anonymous function to combine all terms past the first # to extract space group name space_group = reduce((x, y) -> x * " " * y, line[2:end]) - space_group = split(space_group, ''', keepempty=false)[1] + space_group = split(space_group, [''', '"'], keepempty=false)[1] if space_group == "P1" || space_group == "P 1" || space_group == "-P1" # simplify by only having one P1 space_group name From 0852f85dc07f2d5a2659dc497f3246ed01ac1030 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 15 Aug 2019 11:59:09 -0700 Subject: [PATCH 037/105] fix naming for framework addition, add type for param in framework addition --- src/Crystal.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9b8062b83..696562416 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1041,12 +1041,12 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) return box_flag && charges_flag && atoms_flag && symmetry_flag end -function Base.:+(frameworks::Framework...; check_overlap=true) - new_framework = Framework("", frameworks[1].box, - Atoms(Array{Symbol, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), - Charges(Array{Float64, 1}(undef, 0), Array{Float64, 2}(undef, 3, 0)), - frameworks[1].symmetry, frameworks[1].space_group, frameworks[1].is_p1) - for f in frameworks +function Base.:+(frameworks::Framework...; check_overlap::Bool=true) + new_framework = deepcopy(frameworks[1]) + for (i, f) in enumerate(frameworks) + if i == 1 + continue + end @assert isapprox(new_framework.box, f.box) @sprintf("Framework %s has a different box\n", f.name) @assert is_symmetry_equal(new_framework.symmetry, f.symmetry) @sprintf("Framework %s has different symmetry rules\n", f.name) @assert new_framework.space_group == f.space_group @@ -1054,8 +1054,8 @@ function Base.:+(frameworks::Framework...; check_overlap=true) new_atoms = new_framework.atoms + f.atoms new_charges = new_framework.charges + f.charges - new_framework = Framework(new_framework.name * "_" * f.name, new_framework.box, - new_atoms, new_charges, new_framework.symmetry, + new_framework = Framework(split(new_framework.name, ".")[1] * "_" * split(f.name, ".")[1], + new_framework.box, new_atoms, new_charges, new_framework.symmetry, new_framework.space_group, new_framework.is_p1) end if check_overlap From a953f691f442be1f2c9495255329f9072c465bd8 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 23 Aug 2019 12:38:33 -0700 Subject: [PATCH 038/105] increase precision for comparing atoms and charges, allow out of order as long as (species/q, xf) tuples match, set low precision for p1 tests --- src/Crystal.jl | 6 +++--- src/Matter.jl | 26 ++++++++------------------ test/crystal_test.jl | 6 +++--- 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 696562416..ecf0c1232 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1023,7 +1023,7 @@ function Base.show(io::IO, framework::Framework) end # TODO add something comparing symmetry rules -function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) +function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checknames::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) return false @@ -1035,8 +1035,8 @@ function Base.isapprox(f1::Framework, f2::Framework; checknames::Bool=false) if f1.atoms.n_atoms != f2.atoms.n_atoms return false end - charges_flag = isapprox(f1.charges, f2.charges) - atoms_flag = isapprox(f1.atoms, f2.atoms) + charges_flag = isapprox(f1.charges, f2.charges; atol=atol) + atoms_flag = isapprox(f1.atoms, f2.atoms; atol=atol) symmetry_flag = is_symmetry_equal(f1.symmetry, f2.symmetry) return box_flag && charges_flag && atoms_flag && symmetry_flag end diff --git a/src/Matter.jl b/src/Matter.jl index 64187c594..0fea9b5b0 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -20,16 +20,11 @@ end # compute n_species automatically from array sizes Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) -Base.isapprox(a1::Atoms, a2::Atoms) = issetequal( - Set([(round.(a1.xf[:, i], digits=2), a1.species[i]) for i in 1:a1.n_atoms]), - Set([(round.(a2.xf[:, i], digits=2), a2.species[i]) for i in 1:a2.n_atoms])) +Base.isapprox(a1::Atoms, a2::Atoms; atol::Float64=1e-6) = issetequal( + Set([(round.(a1.xf[:, i], digits=Int(abs(log10(atol)))), a1.species[i]) for i in 1:a1.n_atoms]), + Set([(round.(a2.xf[:, i], digits=Int(abs(log10(atol)))), a2.species[i]) for i in 1:a2.n_atoms])) -function Base.:+(a1::Atoms, a2::Atoms) - new_species = [a1.species; a2.species] - new_xf = [a1.xf a2.xf] - - return Atoms(a1.n_atoms + a2.n_atoms, new_species, new_xf) -end +Base.:+(a1::Atoms, a2::Atoms) = Atoms(a1.n_atoms + a2.n_atoms, [a1.species; a2.species], [a1.xf a2.xf]) """ Data structure holds a set of point charges and their positions in fractional coordinates. @@ -53,13 +48,8 @@ end # compute n_charges automatically from array sizes Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) -Base.isapprox(c1::Charges, c2::Charges) = issetequal( - Set([(round.(c1.xf[:, i], digits=2), c1.q[i]) for i in 1:c1.n_charges]), - Set([(round.(c2.xf[:, i], digits=2), c2.q[i]) for i in 1:c2.n_charges])) +Base.isapprox(c1::Charges, c2::Charges; atol::Float64=1e-6) = issetequal( + Set([(round.(c1.xf[:, i], digits=Int(abs(log10(atol)))), c1.q[i]) for i in 1:c1.n_charges]), + Set([(round.(c2.xf[:, i], digits=Int(abs(log10(atol)))), c2.q[i]) for i in 1:c2.n_charges])) -function Base.:+(c1::Charges, c2::Charges) - new_q = [c1.q; c2.q] - new_xf = [c1.xf c2.xf] - - return Charges(c1.n_charges + c2.n_charges, new_q, new_xf) -end +Base.:+(c1::Charges, c2::Charges) = Charges(c1.n_charges + c2.n_charges, [c1.q; c2.q], [c1.xf c2.xf]) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index a4ed8fd39..72280d248 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -55,11 +55,11 @@ using Random P1_framework.atoms.xf .= mod.(P1_framework.atoms.xf, 1.0) P1_framework.charges.xf .= mod.(P1_framework.charges.xf, 1.0) - @test isapprox(non_P1_framework, P1_framework) + @test isapprox(non_P1_framework, P1_framework; atol=1e-2) # test that fractional and cartesian produce same results - @test isapprox(non_P1_framework, non_P1_cartesian) + @test isapprox(non_P1_framework, non_P1_cartesian; atol=1e-2) # test that cartesian and P1 produce same results - @test isapprox(non_P1_cartesian, P1_framework) + @test isapprox(non_P1_cartesian, P1_framework; atol=1e-2) # test that incorrect file formats throw proper errors @test_throws ErrorException Framework("non_P1_no_symmetry.cif") # test that a file with no atoms throws error From 2b60d62c9153cc97585dd243b63873672cde54c6 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 23 Aug 2019 12:51:31 -0700 Subject: [PATCH 039/105] make P1 assertion a function, add it to functions exported, update gcmc and henry simulations --- src/Crystal.jl | 15 +++++++++++++++ src/GCMC.jl | 18 +++--------------- src/Henry.jl | 6 +----- src/PorousMaterials.jl | 2 +- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index ecf0c1232..51f8298b8 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -861,6 +861,21 @@ function is_symmetry_equal(sym1::Array{AbstractString, 2}, sym2::Array{AbstractS return issetequal(sym1_set, sym2_set) end +""" + assert_P1_symmetry(framework_to_be_tested) + +Used for making sure that a framework is in P1 symmetry before running +simulations on it. If the structure is not in P1, this will throw an +AssertionError. +""" +function assert_P1_symmetry(framework::Framework) + @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n + try running:\n + \tframework_p1 = apply_symmetry_rules(framework)\n + and pass `framework_p1` into this simulation", + framework.name) +end + """ write_cif(framework, filename) diff --git a/src/GCMC.jl b/src/GCMC.jl index e2b4c34ef..87e4d1f7c 100644 --- a/src/GCMC.jl +++ b/src/GCMC.jl @@ -159,11 +159,7 @@ function stepwise_adsorption_isotherm(framework::Framework, filename_comment::AbstractString="") # simulation only works if framework is in P1 - @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n - try running:\n - \tframework_p1 = apply_symmetry_rules(framework)\n - and pass `framework_p1` into this simulation", - framework.name) + assert_P1_symmetry(framework) results = Dict{String, Any}[] # push results to this array molecules = Molecule[] # initiate with empty framework @@ -223,11 +219,7 @@ function adsorption_isotherm(framework::Framework, filename_comment::AbstractString="") # simulation only works if framework is in P1 - @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n - try running:\n - \tframework_p1 = apply_symmetry_rules(framework)\n - and pass `framework_p1` into this simulation", - framework.name) + assert_P1_symmetry(framework) # make a function of pressure only to facilitate uses of `pmap` run_pressure(pressure::Float64) = gcmc_simulation(framework, molecule, temperature, @@ -324,11 +316,7 @@ function gcmc_simulation(framework::Framework, molecule_::Molecule, temperature: density_grid_species::Union{Nothing, Symbol}=nothing, filename_comment::AbstractString="") # simulation only works if framework is in P1 - @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n - try running:\n - \tframework_p1 = apply_symmetry_rules(framework)\n - and pass `framework_p1` into this simulation", - framework.name) + assert_P1_symmetry(framework) start_time = time() # to avoid changing the outside object `molecule_` inside this function, we make diff --git a/src/Henry.jl b/src/Henry.jl index 1be5ce006..bbbd245be 100644 --- a/src/Henry.jl +++ b/src/Henry.jl @@ -48,11 +48,7 @@ function henry_coefficient(framework::Framework, molecule_::Molecule, temperatur accessibility_grid::Union{Nothing, Grid{Bool}}=nothing) # simulation only works if framework is in P1 - @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n - try running:\n - \tframework_p1 = apply_symmetry_rules(framework)\n - and pass `framework_p1` into this simulation", - framework.name) + assert_P1_symmetry(framework) time_start = time() if verbose diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 0d19e9d93..23fda46f1 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -113,7 +113,7 @@ export Framework, read_crystal_structure_file, remove_overlapping_atoms_and_charges, strip_numbers_from_atom_labels!, chemical_formula, molecular_weight, crystal_density, construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, - is_symmetry_equal, apply_symmetry_rules, + is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, From 67c24944d1b3211c404d54b4db979c4456550c4d Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 12:14:52 -0700 Subject: [PATCH 040/105] finish documentation for snapshot information --- src/GCMC.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/GCMC.jl b/src/GCMC.jl index 87e4d1f7c..f31705823 100644 --- a/src/GCMC.jl +++ b/src/GCMC.jl @@ -260,6 +260,9 @@ end load_checkpoint_file=false, show_progress_bar=false, checkpoint=Dict(), write_checkpoints=false, checkpoint_frequency=100, + write_adsorbate_snapshots=false, + snapshot_frequency=1, calculate_density_grid=false, + density_grid_dx=1.0, density_grid_species=nothing, filename_comment="") Runs a grand-canonical (μVT) Monte Carlo simulation of the adsorption of a molecule in a From 3cc178d25ac9012b02c298a094bec1108e89c6b2 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 12:16:49 -0700 Subject: [PATCH 041/105] add docs for all gcmc sims --- src/GCMC.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/GCMC.jl b/src/GCMC.jl index f31705823..e56c94b87 100644 --- a/src/GCMC.jl +++ b/src/GCMC.jl @@ -129,6 +129,9 @@ end verbose=true, ewald_precision=1e-6, eos=:ideal, load_checkpoint_file=false, checkpoint=Dict(), write_checkpoints=false, checkpoint_frequency=50, + write_adsorbate_snapshots=false, + snapshot_frequency=1, calculate_density_grid=false, + density_grid_dx=1.0, density_grid_species=nothing, filename_comment="", show_progress_bar=false) Run a set of grand-canonical (μVT) Monte Carlo simulations in series. Arguments are the @@ -193,6 +196,9 @@ end verbose=true, ewald_precision=1e-6, eos=:ideal, load_checkpoint_file=false, checkpoint=Dict(), write_checkpoints=false, checkpoint_frequency=50, + write_adsorbate_snapshots=false, + snapshot_frequency=1, calculate_density_grid=false, + density_grid_dx=1.0, density_grid_species=nothing, filename_comment="", show_progress_bar=false) Run a set of grand-canonical (μVT) Monte Carlo simulations in parallel. Arguments are the From b6ef3116ee722e9dbe0dcf801599752fc1f42971 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 13:18:36 -0700 Subject: [PATCH 042/105] updated docs for reading in crystal structure files --- docs/make.jl | 2 +- docs/src/guides/faq.md | 9 +++++---- docs/src/manual/boxes_crystals_grids.md | 9 ++++++++- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 40228b6ea..1308ad6bf 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -39,7 +39,7 @@ using PorousMaterials repo = "github.com/SimonEnsemble/PorousMaterials.jl.git", # This is a link to the main repo and the master branch # target = "build", - julia = "1.0", + julia = "1.2", osname = "linux", deps = Deps.pip("mkdocs", "mkdocs-windmill", "pymdown-extensions") # These are dependencies for the site, not the package ) diff --git a/docs/src/guides/faq.md b/docs/src/guides/faq.md index e828abb3e..6d42295b9 100755 --- a/docs/src/guides/faq.md +++ b/docs/src/guides/faq.md @@ -15,8 +15,9 @@ Yes! See [here](https://github.com/JuliaLang/IJulia.jl). **How can I convert my `.cif` into P1 symmetry for `PorousMaterials.jl`?** -We hope someone will contribute this feature to `PorousMaterials.jl` eventually. For now, you can use [OpenBabel](http://openbabel.org/wiki/Main_Page): +`PorousMaterials.jl` will automatically do this for you! It looks for the +`_symmetry_equiv_pos_as_xyz` and use those rules to replicate the lower level +symmetry structure into P1 symmetry. -``` -obabel -icif non-P1.cif -ocif -O P1.cif --fillUC strict -``` +It is important to note that `PorousMaterials.jl` will read in the space group +name, but it does **NOT** use this for converting your structure to P1. diff --git a/docs/src/manual/boxes_crystals_grids.md b/docs/src/manual/boxes_crystals_grids.md index d788f7976..c2f645d73 100644 --- a/docs/src/manual/boxes_crystals_grids.md +++ b/docs/src/manual/boxes_crystals_grids.md @@ -1,6 +1,6 @@ ## Loading in Crystal Structure Files -Place `.cif` and `.cssr` crystal structure files in `data/crystals`. `PorousMaterials.jl` currently takes crystals in P1 symmetry only. From here you can start julia and do the following to load a framework and start working with it. +Place `.cif` and `.cssr` crystal structure files in `data/crystals`. `PorousMaterials.jl` can load in `.cif` files of any symmetry as long as the symmetry operations are included. From here you can start julia and do the following to load a framework and start working with it. ```julia using PorousMaterials @@ -21,6 +21,13 @@ Number of atoms = 120 Number of charges = 0 Chemical formula: Dict(:H=>8,:S=>1,:Ca=>1,:O=>6,:C=>14) ``` + +If the file is not in P1 symmetry, it will be converted within the framework reader and this message will be displayed. + +```julia +"Name_of_file.cif is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl." +``` + ## Building Blocks of PorousMaterials: Bravais lattice From e3e97f340443ffd7764cab89104ee2cbcc530282 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 13:28:53 -0700 Subject: [PATCH 043/105] add option for four argument Framework for simplicity --- src/Crystal.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index 51f8298b8..f0ee46273 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -11,15 +11,22 @@ struct Framework is_p1::Bool end +Framework(name::String, box::Box, atoms::Atoms, Charges::Charges) = Framework( + name, box, atoms, charges, [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]], "P1", true) + """ framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, remove_overlap=false) framework = Framework(name, box, atoms, charges, symmetry, space_group, is_p1) + framework = Framework(name, box, atoms, charges) Read a crystal structure file (.cif or .cssr) and populate a `Framework` data structure, or construct a `Framework` data structure directly. +If the framework is constructed using the `Framework(name, box, atoms, charges)` +function it is assumed it is in P1 symmetry. + # Arguments - `filename::AbstractString`: the name of the crystal structure file (include ".cif" or ".cssr") read from `joinpath(PorousMaterials.PATH_TO_DATA, "structures")`. - `check_charge_neutrality::Bool`: check for charge neutrality From a7e3bb116353f4906cc22adc4fc85903fbeaaf7b Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 13:49:10 -0700 Subject: [PATCH 044/105] add a guide for reading in structures in non-P1 and converting them later --- docs/src/manual/boxes_crystals_grids.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/src/manual/boxes_crystals_grids.md b/docs/src/manual/boxes_crystals_grids.md index c2f645d73..1cdfb6ca2 100644 --- a/docs/src/manual/boxes_crystals_grids.md +++ b/docs/src/manual/boxes_crystals_grids.md @@ -25,7 +25,23 @@ Chemical formula: Dict(:H=>8,:S=>1,:Ca=>1,:O=>6,:C=>14) If the file is not in P1 symmetry, it will be converted within the framework reader and this message will be displayed. ```julia -"Name_of_file.cif is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl." +┌ Warning: Name_of_file.cif is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl. +└ @ PorousMaterials ~/osu_undergrad/simon_ensemble/PorousMaterials.jl/src/Crystal.jl:284 +``` + +PorousMaterials also gives the option to read in structures in the lower level +symmetries and convert them to P1 before simulation. + +```julia +framework = Framework("ORIVOC_clean.cif"; remove_overlap=true, convert_to_p1=false) +# the remove_overlap argument is specific to this structure, not all frameworks need it + +### + Perform any operation on the structure while it is not in P1 +### + +framework = apply_symmetry_rules(framework) +# the framework is now in P1 and it can be used in simulations ``` @@ -123,8 +139,10 @@ write_cube(grid, "CH4_in_SBMOF1.cube") crystal_density replicate(::Framework, ::Tuple{Int, Int, Int}) charged(::Framework; ::Bool) - write_cif assign_charges + apply_symmetry_rules + is_symmetry_equal + write_cif ``` ## Grids From c7642fa860943b90d4d72c1f09ac520b966440cc Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 13:59:09 -0700 Subject: [PATCH 045/105] add space group to documentation --- src/Crystal.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index f0ee46273..9942fbf0d 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -47,6 +47,8 @@ function it is assumed it is in P1 symmetry. - `symmetry::Array{Function, 2}`: 2D array of anonymous functions that represent the symmetry operations. If the structure is in P1 there will be one symmetry operation. +- `space_group::AbstractString`: The name of the space group. This is stored + so that it can be written out again in the write_cif function - `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This is used before any simulations such as GCMC and Henry Coefficient """ @@ -884,7 +886,7 @@ function assert_P1_symmetry(framework::Framework) end """ - write_cif(framework, filename) + write_cif(framework, filename; fractional=true) Write a `framework::Framework` to a .cif file with `filename::AbstractString`. If `filename` does not include the .cif extension, it will automatically be added. From e8fd725b2ab61f0c8db13eb5115c44ae8a35667f Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 16:08:24 -0700 Subject: [PATCH 046/105] start bond branch, create infer_bonds function, BondingRule struct, and add bonds to framework docs. --- src/Crystal.jl | 62 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9942fbf0d..5d34b4bbb 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -6,19 +6,36 @@ struct Framework box::Box atoms::Atoms charges::Charges + bonds::SimpleGraph symmetry::Array{AbstractString, 2} space_group::AbstractString is_p1::Bool end Framework(name::String, box::Box, atoms::Atoms, Charges::Charges) = Framework( - name, box, atoms, charges, [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]], "P1", true) + name, box, atoms, charges, SimpleGraph(atoms.n_atoms), + [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]], "P1", true) + +# struct for holding bonding information +""" + bonding_rule = BondingRule(:Ca, :O, 0.4, 2.0) + bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), + BondingRule(:*, :*, 0.4, 1.9)] + +A rule for determining if two atoms within a framework are bonded. +""" +struct BondingRule + species_i::Symbol + species_j::Symbol + min_dist::Float64 + max_dist::Float64 +end """ framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, remove_overlap=false) - framework = Framework(name, box, atoms, charges, symmetry, space_group, is_p1) + framework = Framework(name, box, atoms, charges, bonds, symmetry, space_group, is_p1) framework = Framework(name, box, atoms, charges) Read a crystal structure file (.cif or .cssr) and populate a `Framework` data structure, @@ -35,6 +52,8 @@ function it is assumed it is in P1 symmetry. - `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. - `convert_to_p1::Bool`: If the structure is not in P1 it will be converted to P1 symmetry using the symmetry rules +- `read_bonds_from_file::Bool`: Whether or not to read bonding information from + cif file. If false, the bonds can be inferred later # Returns - `framework::Framework`: A framework containing the crystal structure information @@ -44,6 +63,8 @@ function it is assumed it is in P1 symmetry. - `box::Box`: unit cell (Bravais Lattice) - `atoms::Atoms`: list of Atoms in crystal unit cell - `charges::Charges`: list of point charges in crystal unit cell +- `bonds::SimpleGraph`: Unweighted, undirected graph showing all of the atoms + that are bonded within the framework - `symmetry::Array{Function, 2}`: 2D array of anonymous functions that represent the symmetry operations. If the structure is in P1 there will be one symmetry operation. @@ -54,7 +75,8 @@ function it is assumed it is in P1 symmetry. """ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, - remove_overlap::Bool=false, convert_to_p1::Bool=true) + remove_overlap::Bool=false, convert_to_p1::Bool=true, + read_bonds_from_file::Bool=true) # Read file extension. Ensure we can read the file type extension = split(filename, ".")[end] if ! (extension in ["cif", "cssr"]) @@ -395,6 +417,7 @@ construct a new `Framework`. Note `replicate(framework, (1, 1, 1))` returns the - `replicated_frame::Framework`: Replicated framework """ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) + @assert ne(framework.bonds) == 0 @sprintf("The framework %s has bonds within it. Remove the bonds to replicate, and then use `infer_bonds(framework)` to recalculate bond information", framework.name) # determine number of atoms in replicated framework n_atoms = size(framework.atoms.xf, 2) * repfactors[1] * repfactors[2] * repfactors[3] @@ -885,6 +908,39 @@ function assert_P1_symmetry(framework::Framework) framework.name) end +""" + infer_bonds!(framework, bonding_rules) + +Populate the bonds in the framework object based on the bonding rules. If a +pair doesn't have a suitable rule then they will not be considered bonded. + +`:*` is considered a wildcard and can be substituted for any species. It is a +good idea to include a bonding rule between two `:*` to allow any atoms to bond +as long as they are close enough. +""" +function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}) + @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them before inferring new ones.", framework.name) + + # loop over every atom + for i in 1:framework.n_atoms + # loop over every unique pair of atoms + for j in i+1:framework.n_atoms + # loop over possible bonding rules + for br in bonding_rules + # determine if the types are correct + if (framework.atoms.species[i] == bf.species_i && framework.atoms.species[j] == br.species_j) || + (framework.atoms.species[j] == bf.species_i && framework.atoms.species[i] == br.species_j) + # determine if they are within range + dist = norm(framework.atoms.xf[:, i] - framework.atoms.xf[:, j]) + if br.min_dist < dist && dist < br.max_dist + add_edge!(framework.bonds, i, j) + end + end + end + end + end +end + """ write_cif(framework, filename; fractional=true) From 622348e86d7f098d31cfc6c65be88a10bd5360ec Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 16:53:39 -0700 Subject: [PATCH 047/105] update base framework constructor to require name, box, atoms, and charges, everything else is keyword arg --- src/Crystal.jl | 40 ++++++++++++++++++++++++---------------- test/crystal_test.jl | 20 ++++++-------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 5d34b4bbb..789a5a80c 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -12,9 +12,12 @@ struct Framework is_p1::Bool end -Framework(name::String, box::Box, atoms::Atoms, Charges::Charges) = Framework( - name, box, atoms, charges, SimpleGraph(atoms.n_atoms), - [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]], "P1", true) +function Framework(name::AbstractString, box::Box, atoms::Atoms, charges::Charges; + bonds::SimpleGraph=SimpleGraph(atoms.n_atoms), + symmetry::Array{AbstractString, 2}=[Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]], + space_group::AbstractString="P1", is_p1::Bool=true) + return Framework(name, box, atoms, charges, bonds, symmetry, space_group, is_p1) +end # struct for holding bonding information """ @@ -35,8 +38,8 @@ end framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, remove_overlap=false) - framework = Framework(name, box, atoms, charges, bonds, symmetry, space_group, is_p1) - framework = Framework(name, box, atoms, charges) + framework = Framework(name, box, atoms, charges; bonds=SimpleGraph(atoms.n_atoms), + symmetry=["x", "y", "z"], space_group="P1", is_p1=true) Read a crystal structure file (.cif or .cssr) and populate a `Framework` data structure, or construct a `Framework` data structure directly. @@ -376,7 +379,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, idx_nz = charge_values .!= 0.0 charges = Charges(charge_values[idx_nz], coords[:, idx_nz]) - framework = Framework(filename, box, atoms, charges, symmetry_rules, space_group, p1_symmetry) + framework = Framework(filename, box, atoms, charges; symmetry=symmetry_rules, space_group=space_group, is_p1=p1_symmetry) if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) @@ -451,7 +454,9 @@ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) @assert (new_charges.n_charges == framework.charges.n_charges * prod(repfactors)) @assert (new_atoms.n_atoms == framework.atoms.n_atoms * prod(repfactors)) - return Framework(framework.name, new_box, new_atoms, new_charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) + return Framework(framework.name, new_box, new_atoms, new_charges, + symmetry=deepcopy(framework.symmetry), + space_group=framework.space_group, is_p1=framework.is_p1) end # doc string in Misc.jl @@ -626,7 +631,9 @@ function remove_overlapping_atoms_and_charges(framework::Framework; atoms = Atoms(framework.atoms.species[atoms_to_keep], atom_coords_to_keep) charges = Charges(framework.charges.q[charges_to_keep], charge_coords_to_keep) - new_framework = Framework(framework.name, framework.box, atoms, charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) + new_framework = Framework(framework.name, framework.box, atoms, charges, + symmetry=deepcopy(framework.symmetry), + space_group=framework.space_group, is_p1=framework.is_p1) @assert (! atom_overlap(new_framework, overlap_tol=atom_overlap_tol)) @assert (! charge_overlap(new_framework, overlap_tol=charge_overlap_tol)) @@ -781,7 +788,7 @@ return the new framework. # Returns - `P1_framework::Framework`: The framework after it has been converted to P1 - symmetry. The new symmetry rules will be the P1 symemtry rules + symmetry. The new symmetry rules will be the P1 symmetry rules """ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, @@ -793,7 +800,7 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo # for each symmetry rule for i in 1:size(framework.symmetry, 2) - # loop over all atoms in lower level symemtry + # loop over all atoms in lower level symmetry for j in 1:size(framework.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates new_atom_xfs = [new_atom_xfs [Base.invokelatest.( @@ -812,11 +819,9 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo new_charge_qs = [new_charge_qs; framework.charges.q] end - new_symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] - new_framework = Framework(framework.name, framework.box, Atoms(new_atom_species, new_atom_xfs), - Charges(new_charge_qs, new_charge_xfs), new_symmetry_rules, "P1", true) + Charges(new_charge_qs, new_charge_xfs)) if check_charge_neutrality if ! charge_neutral(new_framework, net_charge_tol) @@ -1077,7 +1082,9 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 charges = Charges(charge_vals, charge_coords) # construct new framework - new_framework = Framework(framework.name, framework.box, framework.atoms, charges, deepcopy(framework.symmetry), framework.space_group, framework.is_p1) + new_framework = Framework(framework.name, framework.box, framework.atoms, charges, + symmetry=deepcopy(framework.symmetry), + space_group=framework.space_group, is_p1=framework.is_p1) # check for charge neutrality if abs(total_charge(new_framework)) > net_charge_tol @@ -1135,8 +1142,9 @@ function Base.:+(frameworks::Framework...; check_overlap::Bool=true) new_charges = new_framework.charges + f.charges new_framework = Framework(split(new_framework.name, ".")[1] * "_" * split(f.name, ".")[1], - new_framework.box, new_atoms, new_charges, new_framework.symmetry, - new_framework.space_group, new_framework.is_p1) + new_framework.box, new_atoms, new_charges, + symmetry=new_framework.symmetry,space_group=new_framework.space_group, + is_p1=new_framework.is_p1) end if check_overlap if atom_overlap(new_framework) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 72280d248..d52af4ad8 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -140,30 +140,22 @@ using Random @test is_symmetry_equal(symmetry_rules_two, symmetry_rules_two_cpy) # test framework addition - f1 = Framework("framework 1", UnitCube(), Atoms( - [:a, :b], + f1 = Framework("framework 1", UnitCube(), Atoms([:a, :b], [1.0 4.0; 2.0 5.0; 3.0 6.0]), - Charges( - [0.1, 0.2], + Charges([0.1, 0.2], [1.0 4.0; 2.0 5.0; - 3.0 6.0]), - deepcopy(symmetry_rules), - "P1", true) - f2 = Framework("framework 2", UnitCube(), Atoms( - [:c, :d], + 3.0 6.0])) + f2 = Framework("framework 2", UnitCube(), Atoms([:c, :d], [7.0 10.0; 8.0 11.0; 9.0 12.0]), - Charges( - [0.3, 0.4], + Charges([0.3, 0.4], [7.0 10.0; 8.0 11.0; - 9.0 12.0]), - deepcopy(symmetry_rules), - "P1", true) + 9.0 12.0])) f3 = f1 + f2 @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) From 9a97e6509bf6f16834df85b9e6cc07f9135ae26a Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 26 Aug 2019 17:32:31 -0700 Subject: [PATCH 048/105] fix infer_bonds to include wildcards, add test for replication assertion error --- src/Crystal.jl | 30 +++++++++++++++++++++--------- src/PorousMaterials.jl | 9 +++++---- test/crystal_test.jl | 7 +++++++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 789a5a80c..85b5d0d9e 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -927,19 +927,31 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them before inferring new ones.", framework.name) # loop over every atom - for i in 1:framework.n_atoms + for i in 1:framework.atoms.n_atoms # loop over every unique pair of atoms - for j in i+1:framework.n_atoms + for j in i+1:framework.atoms.n_atoms # loop over possible bonding rules for br in bonding_rules # determine if the types are correct - if (framework.atoms.species[i] == bf.species_i && framework.atoms.species[j] == br.species_j) || - (framework.atoms.species[j] == bf.species_i && framework.atoms.species[i] == br.species_j) - # determine if they are within range - dist = norm(framework.atoms.xf[:, i] - framework.atoms.xf[:, j]) - if br.min_dist < dist && dist < br.max_dist - add_edge!(framework.bonds, i, j) - end + # anything goes, reached the final bonding rule (if set up right) + species_match = false + if br.species_i == :* && br.species_j == :* + species_match = true + elseif br.species_i == :* && (framework.atoms.species[i] == br.species_j || + framework.atoms.species[j] == br.species_j) + species_match = true + elseif br.species_j == :* && (framework.atoms.species[i] == br.species_i || + framework.atoms.species[j] == br.species_i) + species_match = true + elseif (framework.atoms.species[i] == br.species_i && framework.atoms.species[j] == br.species_j) || + (framework.atoms.species[j] == br.species_i && framework.atoms.species[i] == br.species_j) + species_match = true + end + # determine if they are within range + dist = norm(framework.atoms.xf[:, i] - framework.atoms.xf[:, j]) + if species_match && br.min_dist < dist && dist < br.max_dist + add_edge!(framework.bonds, i, j) + break end end end diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 23fda46f1..09a4f6c0c 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -110,10 +110,11 @@ export read_xyz, read_cpk_colors, read_atomic_radii, write_xyz, fit_adsorption_isotherm, # Crystal.jl - Framework, read_crystal_structure_file, remove_overlapping_atoms_and_charges, - strip_numbers_from_atom_labels!, chemical_formula, molecular_weight, crystal_density, - construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, - is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, + Framework, BondingRule, read_crystal_structure_file, + remove_overlapping_atoms_and_charges, strip_numbers_from_atom_labels!, + chemical_formula, molecular_weight, crystal_density, construct_box, + replicate, read_atomic_masses, charged, write_cif, assign_charges, + is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, diff --git a/test/crystal_test.jl b/test/crystal_test.jl index d52af4ad8..fbd545f76 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -117,6 +117,13 @@ using Random sbmof = Framework("SBMOF-1.cif") replicated_sbmof = replicate(sbmof, (1, 1, 1)) @test isapprox(sbmof, replicated_sbmof) + # test replication no bonds assertion + sbmof_bonds = Framework("SBMOF-1.cif") + bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), + BondingRule(:Ca, :*, 0.4, 2.3), + BondingRule(:*, :*, 0.4, 1.9)] + infer_bonds!(sbmof_bonds, bonding_rules) + @test_throws AssertionError repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) From c855ee2257fd8abf2d1c6bbd1b76a6881df9b9e8 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 27 Aug 2019 15:01:54 -0700 Subject: [PATCH 049/105] add function for removing bonds from a framework --- src/Crystal.jl | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index 85b5d0d9e..079c0a122 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -913,6 +913,17 @@ function assert_P1_symmetry(framework::Framework) framework.name) end +""" + remove_bonds!(framework) + +Remove all bonds from a framework structure. +""" +function remove_bonds!(framework::Framework) + while ne(framework.bonds) > 0 + rem_edge!(framework.bonds, collect(edges(framework))[1].src, collect(edges(framework))[1].dst) + end +end + """ infer_bonds!(framework, bonding_rules) From e2aec85dc43c424fce3b316fc3895c2607cab7a0 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 27 Aug 2019 15:12:10 -0700 Subject: [PATCH 050/105] fix tests add new testing info for removing bonds --- src/Crystal.jl | 2 +- test/crystal_test.jl | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 079c0a122..2394ca649 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -935,7 +935,7 @@ good idea to include a bonding rule between two `:*` to allow any atoms to bond as long as they are close enough. """ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}) - @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them before inferring new ones.", framework.name) + @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them with the `remove_bonds!` function before inferring new ones.", framework.name) # loop over every atom for i in 1:framework.atoms.n_atoms diff --git a/test/crystal_test.jl b/test/crystal_test.jl index fbd545f76..9edd25c73 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -120,10 +120,15 @@ using Random # test replication no bonds assertion sbmof_bonds = Framework("SBMOF-1.cif") bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), - BondingRule(:Ca, :*, 0.4, 2.3), + BondingRule(:Ca, :O, 0.4, 2.3), BondingRule(:*, :*, 0.4, 1.9)] infer_bonds!(sbmof_bonds, bonding_rules) - @test_throws AssertionError + @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) + # other bond info tests + # TODO find more robust test/confirm these are the correct numbers + @test ne(sbmof_bonds.bonds) == 5970 + remove_bonds!(sbmof_bonds) + @test ne(sbmof_bonds) == 0 repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) From 8c032ce38ef50825e942645c44c64786386727e5 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 27 Aug 2019 15:38:46 -0700 Subject: [PATCH 051/105] fix tests --- src/Crystal.jl | 2 +- src/PorousMaterials.jl | 1 + test/crystal_test.jl | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 2394ca649..62b70fa10 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -920,7 +920,7 @@ Remove all bonds from a framework structure. """ function remove_bonds!(framework::Framework) while ne(framework.bonds) > 0 - rem_edge!(framework.bonds, collect(edges(framework))[1].src, collect(edges(framework))[1].dst) + rem_edge!(framework.bonds, collect(edges(framework.bonds))[1].src, collect(edges(framework.bonds))[1].dst) end end diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 09a4f6c0c..7291ab8af 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -115,6 +115,7 @@ export chemical_formula, molecular_weight, crystal_density, construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, + remove_bonds!, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 9edd25c73..c3b067a44 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -3,6 +3,7 @@ module Crystal_Test using PorousMaterials using OffsetArrays using LinearAlgebra +using LightGraphs using Test using JLD2 using Statistics @@ -128,7 +129,7 @@ using Random # TODO find more robust test/confirm these are the correct numbers @test ne(sbmof_bonds.bonds) == 5970 remove_bonds!(sbmof_bonds) - @test ne(sbmof_bonds) == 0 + @test ne(sbmof_bonds.bonds) == 0 repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) From acb60d41b3d0e590c59f3c122af2a824bf483f0a Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 27 Aug 2019 17:39:26 -0700 Subject: [PATCH 052/105] add bond comparison function, add writing bond info functionality to write_cif --- src/Crystal.jl | 89 ++++++++++++++++++++++++++++++++++-------- src/PorousMaterials.jl | 2 +- test/crystal_test.jl | 6 ++- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 62b70fa10..6b040f49f 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -959,8 +959,10 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} species_match = true end # determine if they are within range - dist = norm(framework.atoms.xf[:, i] - framework.atoms.xf[:, j]) - if species_match && br.min_dist < dist && dist < br.max_dist + dxf = framework.atoms.xf[:, i] - framework.atoms.xf[:, j] + nearest_image!(dxf) + norm_c = norm(framework.box.f_to_c * dxf) + if species_match && br.min_dist < norm_c && norm_c < br.max_dist add_edge!(framework.bonds, i, j) break end @@ -969,6 +971,34 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} end end +""" + bonds_equal = compare_bonds_in_framework(framework1, framework2) + +Returns whether the bonds defined in framework1 are the same as the bonds +defined in framework2. +""" +function compare_bonds_in_framework(f1::Framework, f2::Framework) + if ne(f1.bonds) != ne(f2.bonds) + return false + end + + other_bonds = deepcopy(f2.bonds) + + for edge_i in collect(edges(f1.bonds)) + set_i = Set([(f1.atoms.xf[:, edge_i.src], f1.atoms.species[edge_i.src]), + (f1.atoms.xf[:, edge_i.dst], f1.atoms.species[edge_i.dst])]) + for edge_j in collect(edges(other_bonds)) + set_j = Set([(f2.atoms.xf[:, edge_j.src], f2.atoms.species[edge_j.src]), + (f2.atoms.xf[:, edge_j.dst], f2.atoms.species[edge_j.dst])]) + if issetequal(set_i, set_j) + rem_edge!(other_bonds, edge_j.src, edge_j.dst) + break + end + end + end + return ne(other_bonds) == 0 +end + """ write_cif(framework, filename; fractional=true) @@ -979,6 +1009,15 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B if charged(framework) && (framework.atoms.n_atoms != framework.charges.n_charges) error("write_cif assumes equal numbers of Charges and Atoms (or zero charges)") end + + # create dictionary for tracking label numbers + label_numbers = Dict{Symbol, Int}() + for atom in framework.atoms.species + if !haskey(label_numbers, atom) + label_numbers[atom] = 1 + end + end + # append ".cif" to filename if it doesn't already have the extension if ! occursin(".cif", filename) filename *= ".cif" @@ -992,15 +1031,15 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B @printf(cif_file, "data_%s_PM\n", split(framework.name, ".")[1]) end - @printf(cif_file, "_symmetry_space_group_name_H-M '%s'\n", framework.space_group) + @printf(cif_file, "_symmetry_space_group_name_H-M\t'%s'\n", framework.space_group) - @printf(cif_file, "_cell_length_a %f\n", framework.box.a) - @printf(cif_file, "_cell_length_b %f\n", framework.box.b) - @printf(cif_file, "_cell_length_c %f\n", framework.box.c) + @printf(cif_file, "_cell_length_a\t%f\n", framework.box.a) + @printf(cif_file, "_cell_length_b\t%f\n", framework.box.b) + @printf(cif_file, "_cell_length_c\t%f\n", framework.box.c) - @printf(cif_file, "_cell_angle_alpha %f\n", framework.box.α * 180.0 / pi) - @printf(cif_file, "_cell_angle_beta %f\n", framework.box.β * 180.0 / pi) - @printf(cif_file, "_cell_angle_gamma %f\n", framework.box.γ * 180.0 / pi) + @printf(cif_file, "_cell_angle_alpha\t%f\n", framework.box.α * 180.0 / pi) + @printf(cif_file, "_cell_angle_beta\t%f\n", framework.box.β * 180.0 / pi) + @printf(cif_file, "_cell_angle_gamma\t%f\n", framework.box.γ * 180.0 / pi) @printf(cif_file, "_symmetry_Int_Tables_number 1\n\n") @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n") @@ -1009,7 +1048,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B end @printf(cif_file, "\n") - @printf(cif_file, "loop_\n_atom_site_label\n") + @printf(cif_file, "loop_\n_atom_site_label\n_atom_site_type_symbol\n") if fractional @printf(cif_file, "_atom_site_fract_x\n_atom_site_fract_y\n_atom_site_fract_z\n") else @@ -1017,6 +1056,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B end @printf(cif_file, "_atom_site_charge\n") + idx_to_label = Array{AbstractString, 1}(undef, framework.atoms.n_atoms) for i = 1:framework.atoms.n_atoms q = 0.0 if charged(framework) @@ -1025,16 +1065,33 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B error("write_cif assumes charges correspond to LJspheres") end end + # print label and type symbol + @printf(cif_file, "%s\t%s\t", string(framework.atoms.species[i]) * + string(label_numbers[framework.atoms.species[i]]), + framework.atoms.species[i]) + # store label for this atom idx + idx_to_label[i] = string(framework.atoms.species[i]) * + string(label_numbers[framework.atoms.species[i]]) + # increment label + label_numbers[framework.atoms.species[i]] += 1 if fractional - @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], - framework.atoms.xf[:, i]..., q) + @printf(cif_file, "%f\t%f\t%f\t%f\n", framework.atoms.xf[:, i]..., q) else - @printf(cif_file, "%s %f %f %f %f\n", framework.atoms.species[i], - (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) + @printf(cif_file, "%f\t%f\t%f\t%f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) end - end - close(cif_file) + end + + # print column names for bond information + @printf(cif_file, "\nloop_\n_geom_bond_atom_site_label_1\n_geom_bond_atom_site_label_2\n_geom_bond_distance\n") + + for edge in collect(edges(framework.bonds)) + dxf = framework.atoms.xf[:, edge.src] - framework.atoms.xf[:, edge.dst] + nearest_image!(dxf) + @printf(cif_file, "%s\t%s\t%0.5f\n", idx_to_label[edge.src], idx_to_label[edge.dst], + norm(dxf)) + end + close(cif_file) end """ diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 7291ab8af..d6ec17cfb 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -115,7 +115,7 @@ export chemical_formula, molecular_weight, crystal_density, construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, - remove_bonds!, + remove_bonds!, compare_bonds_in_framework, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, diff --git a/test/crystal_test.jl b/test/crystal_test.jl index c3b067a44..e16e6ba74 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -127,9 +127,13 @@ using Random @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) # other bond info tests # TODO find more robust test/confirm these are the correct numbers - @test ne(sbmof_bonds.bonds) == 5970 + @test ne(sbmof_bonds.bonds) == 128 + sbmof_bonds_copy = Framework("SBMOF-1.cif") + infer_bonds!(sbmof_bonds_copy, bonding_rules) + @test compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) remove_bonds!(sbmof_bonds) @test ne(sbmof_bonds.bonds) == 0 + @test !compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) From 9bbc43507e7d52bac4bd2d47ecef08c6ebbb0dc6 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 28 Aug 2019 12:27:28 -0700 Subject: [PATCH 053/105] restructure cif readder to make it easier to add new read in types in the future. add function for wrapping positions to unit cell --- src/Crystal.jl | 186 +++++++++++++++++++++++++++++++++++++---- src/PorousMaterials.jl | 2 +- 2 files changed, 170 insertions(+), 18 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 6b040f49f..5975f26ea 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -145,6 +145,143 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # checking for information about atom sites and symmetry if line[1] == "loop_" + # creating dictionary of column names to determine what should be done + atom_column_name = "" + # name_to_column is a dictionary that e.g. returns which column contains x fractional coord + # use example: name_to_column["_atom_site_fract_x"] gives 3 + name_to_column = Dict{AbstractString, Int}() + + i += 1 + loop_starts = i + while length(split(lines[i])) == 1 && split(lines[i])[1][1] == '_' + if i == loop_starts + atom_column_name = split(lines[i])[1] + end + name_to_column[split(lines[i])[1]] = i + 1 - loop_starts + # iterate to next line in file + i += 1 + end + + fractional = haskey(name_to_column, "_atom_site_fract_x") && + haskey(name_to_column, "_atom_site_fract_y") && + haskey(name_to_column, "_atom_site_fract_z") + # if the file provides cartesian coordinates + cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && + haskey(name_to_column, "_atom_site_Cartn_y") && + haskey(name_to_column, "_atom_site_Cartn_z") && + ! fractional # if both are provided, will default + # to using fractional, so keep cartesian + # false + + # ===================== + # SYMMETRY READER + # ===================== + if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") + symmetry_info = true + + symmetry_count = 0 + # CSD stores symmetry as one column in a string that ends + # up getting split on the spaces between commas (i.e. its + # not really one column) the length(name_to_column) + 2 + # should catch this hopefully there aren't other weird + # ways of writing cifs... + while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' && !occursin("loop_", lines[i]) + symmetry_count += 1 + line = lines[i] + sym_funcs = split(line, [' ', ',', '''], keepempty=false) + + # store as strings so it can be written out later + new_sym_rule = Array{AbstractString, 1}(undef, 3) + + sym_start = name_to_column["_symmetry_equiv_pos_as_xyz"] - 1 + for j = 1:3 + new_sym_rule[j] = sym_funcs[j + sym_start] + end + + symmetry_rules = [symmetry_rules new_sym_rule] + + i += 1 + end + + @assert symmetry_count == size(symmetry_rules, 2) "number of symmetry rules must match the count" + + # finish reading in symmetry information, skip to next + # iteration of outer while-loop + continue + # ===================== + # FRACTIONAL READER + # ===================== + elseif fractional && ! atom_info + atom_info = true + atom_column_name = "" + for (name, column) in name_to_column + if column == 1 + atom_column_name = name + end + end + + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) + + push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) + coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), + mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] + # If charges present, import them + if haskey(name_to_column, "_atom_site_charge") + push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + else + push!(charges_simple, 0.0) + end + # iterate to next line in file + i += 1 + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue + end + # ===================== + # CARTESIAN READER + # ===================== + elseif cartesian && ! atom_info + atom_info = true + atom_column_name = "" + for (name, column) in name_to_column + if column == 1 + atom_column_name = name + end + end + + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) + + push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) + coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), + parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] + # If charges present, import them + if haskey(name_to_column, "_atom_site_charge") + push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + else + push!(charges_simple, 0.0) + end + # iterate to next line in file + i += 1 + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue + end + # ===================== + # BOND READER + # ===================== + elseif haskey(name_to_column, "_geom_bond_atom_site_label_1") && + haskey(name_to_column, "_geom_bond_atom_site_label_2") && + read_bonds_from_file + end + + +#= ============================================================================ next_line = split(lines[i+1], [' ', '\t']; keepempty=false) # only read in symmetry if the structure is not in P1 symmetry if occursin("_symmetry_equiv_pos", next_line[1]) && !p1_symmetry @@ -261,6 +398,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, continue end end +=# # ========================================================================== end # pick up unit cell lengths @@ -406,6 +544,17 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, return framework end +""" + wrap_atoms_to_unit_cell!(framework) + +Wraps the coordinates of all atom and charge positions to be within the unit +cell defined for the framework. +""" +function wrap_atoms_to_unit_cell!(framework::Framework) + framework.atoms.xf .= mod.(framework.atoms.xf, 1.0) + framework.charges.xf .= mod.(framework.charges.xf, 1.0) +end + """ replicated_frame = replicate(framework, repfactors) @@ -1031,15 +1180,15 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B @printf(cif_file, "data_%s_PM\n", split(framework.name, ".")[1]) end - @printf(cif_file, "_symmetry_space_group_name_H-M\t'%s'\n", framework.space_group) + @printf(cif_file, "_symmetry_space_group_name_H-M \t'%s'\n", framework.space_group) - @printf(cif_file, "_cell_length_a\t%f\n", framework.box.a) - @printf(cif_file, "_cell_length_b\t%f\n", framework.box.b) - @printf(cif_file, "_cell_length_c\t%f\n", framework.box.c) + @printf(cif_file, "_cell_length_a \t%f\n", framework.box.a) + @printf(cif_file, "_cell_length_b \t%f\n", framework.box.b) + @printf(cif_file, "_cell_length_c \t%f\n", framework.box.c) - @printf(cif_file, "_cell_angle_alpha\t%f\n", framework.box.α * 180.0 / pi) - @printf(cif_file, "_cell_angle_beta\t%f\n", framework.box.β * 180.0 / pi) - @printf(cif_file, "_cell_angle_gamma\t%f\n", framework.box.γ * 180.0 / pi) + @printf(cif_file, "_cell_angle_alpha \t%f\n", framework.box.α * 180.0 / pi) + @printf(cif_file, "_cell_angle_beta \t%f\n", framework.box.β * 180.0 / pi) + @printf(cif_file, "_cell_angle_gamma \t%f\n", framework.box.γ * 180.0 / pi) @printf(cif_file, "_symmetry_Int_Tables_number 1\n\n") @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n") @@ -1066,7 +1215,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B end end # print label and type symbol - @printf(cif_file, "%s\t%s\t", string(framework.atoms.species[i]) * + @printf(cif_file, "%s \t%s \t", string(framework.atoms.species[i]) * string(label_numbers[framework.atoms.species[i]]), framework.atoms.species[i]) # store label for this atom idx @@ -1075,21 +1224,24 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B # increment label label_numbers[framework.atoms.species[i]] += 1 if fractional - @printf(cif_file, "%f\t%f\t%f\t%f\n", framework.atoms.xf[:, i]..., q) + @printf(cif_file, "%f \t%f \t%f \t%f\n", framework.atoms.xf[:, i]..., q) else - @printf(cif_file, "%f\t%f\t%f\t%f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) + @printf(cif_file, "%f \t%f \t%f \t%f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) end end - # print column names for bond information - @printf(cif_file, "\nloop_\n_geom_bond_atom_site_label_1\n_geom_bond_atom_site_label_2\n_geom_bond_distance\n") + # only print bond information if it is in the framework + if ne(framework.bonds) > 0 + # print column names for bond information + @printf(cif_file, "\nloop_\n_geom_bond_atom_site_label_1\n_geom_bond_atom_site_label_2\n_geom_bond_distance\n") - for edge in collect(edges(framework.bonds)) - dxf = framework.atoms.xf[:, edge.src] - framework.atoms.xf[:, edge.dst] - nearest_image!(dxf) - @printf(cif_file, "%s\t%s\t%0.5f\n", idx_to_label[edge.src], idx_to_label[edge.dst], - norm(dxf)) + for edge in collect(edges(framework.bonds)) + dxf = framework.atoms.xf[:, edge.src] - framework.atoms.xf[:, edge.dst] + nearest_image!(dxf) + @printf(cif_file, "%s \t%s \t%0.5f\n", idx_to_label[edge.src], idx_to_label[edge.dst], + norm(dxf)) + end end close(cif_file) end diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index d6ec17cfb..14d2fadcb 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -115,7 +115,7 @@ export chemical_formula, molecular_weight, crystal_density, construct_box, replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, - remove_bonds!, compare_bonds_in_framework, + remove_bonds!, compare_bonds_in_framework, wrap_atoms_to_unit_cell!, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, From a463159f5728f9f1f73667b13dfd438566e59429 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 28 Aug 2019 12:55:31 -0700 Subject: [PATCH 054/105] add '_symmetry_space_group_name_H-M' to the orivoc file. Fix issue where sapce group name wasn't changing when converting to P1 in cif reader --- src/Crystal.jl | 1 + test/data/crystals/ORIVOC_clean_P1.cif | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9942fbf0d..1c13652ca 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -294,6 +294,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, charge_values = [charge_values; charges_simple] species = [species; species_simple] end + space_group = "P1" elseif p1_symmetry || !convert_to_p1 coords = deepcopy(coords_simple) charge_values = deepcopy(charges_simple) diff --git a/test/data/crystals/ORIVOC_clean_P1.cif b/test/data/crystals/ORIVOC_clean_P1.cif index 5baccdd8c..26cca6420 100644 --- a/test/data/crystals/ORIVOC_clean_P1.cif +++ b/test/data/crystals/ORIVOC_clean_P1.cif @@ -9,6 +9,7 @@ _cell_angle_beta 90 _cell_angle_gamma 120 _space_group_name_H-M_alt 'P 1' _space_group_name_Hall 'P 1' +_symmetry_space_group_name_H-M 'P 1' loop_ _symmetry_equiv_pos_as_xyz x,y,z From e6f063ea2f908df05734e757b068d7f7db5b8786 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 28 Aug 2019 13:04:04 -0700 Subject: [PATCH 055/105] fix issue with henry tests --- src/Crystal.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 5975f26ea..a678ec672 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -176,7 +176,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # ===================== # SYMMETRY READER # ===================== - if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") + if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") && ! p1_symmetry symmetry_info = true symmetry_count = 0 @@ -188,7 +188,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' && !occursin("loop_", lines[i]) symmetry_count += 1 line = lines[i] - sym_funcs = split(line, [' ', ',', '''], keepempty=false) + sym_funcs = split(line, [' ', ',', ''', '"'], keepempty=false) # store as strings so it can be written out later new_sym_rule = Array{AbstractString, 1}(undef, 3) From b7b9a2f9bb101144855d03f6b47a5d62d784e681 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 28 Aug 2019 14:32:27 -0700 Subject: [PATCH 056/105] working on bond reader --- src/Crystal.jl | 154 ++++++++++--------------------------------------- 1 file changed, 32 insertions(+), 122 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index a678ec672..36900e793 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -101,6 +101,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # default for symmetry rules is P1. # These will be overwritten if the user chooses to read in non-P1 symmetry_rules = Array{AbstractString, 2}(undef, 3, 0) + # creating empty SimpleGraph, might not have any information read in + bonds = SimpleGraph() # used for remembering whether fractional/cartesian coordinates are read in # placed here so it will be defined for the if-stmt after the box is defined fractional = false @@ -121,6 +123,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # used for reading in symmetry options and replications symmetry_info = false atom_info = false + label_num_to_idx = Dict{AbstractString, Int}() while i <= length(lines) line = split(lines[i]) # Skip empty lines @@ -233,13 +236,17 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, else push!(charges_simple, 0.0) end + # add to label_num_to_idx so that bonds can be converted later + if read_bonds_from_file + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + end # iterate to next line in file i += 1 - # finish reading in atom_site information, skip to next - # iteration of outer while-loop - # prevents skipping a line after finishing reading atoms - continue end + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue # ===================== # CARTESIAN READER # ===================== @@ -265,140 +272,42 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, else push!(charges_simple, 0.0) end + # add to label_num_to_idx so that bonds can be converted later + if read_bonds_from_file + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + end # iterate to next line in file i += 1 - # finish reading in atom_site information, skip to next - # iteration of outer while-loop - # prevents skipping a line after finishing reading atoms - continue end + # finish reading in atom_site information, skip to next + # iteration of outer while-loop + # prevents skipping a line after finishing reading atoms + continue # ===================== # BOND READER # ===================== elseif haskey(name_to_column, "_geom_bond_atom_site_label_1") && haskey(name_to_column, "_geom_bond_atom_site_label_2") && read_bonds_from_file - end - + @printf("Inside Bond Reader\n") + # set up graph of correct size + bonds = SimpleGraph(length(species)) + while i <= length(lines) && length(split(lines[i])) == length(name_to_column) + line = split(lines[i]) -#= ============================================================================ - next_line = split(lines[i+1], [' ', '\t']; keepempty=false) - # only read in symmetry if the structure is not in P1 symmetry - if occursin("_symmetry_equiv_pos", next_line[1]) && !p1_symmetry - symmetry_info = true - symmetry_column_name = "" - # name_to_column is a dictionary that e.g. returns which column contains xyz remapping - # use example: name_to_column["_symmetry_equiv_pos_as_xyz"] gives 2 - name_to_column = Dict{AbstractString, Int}() + atom_one_idx = label_num_to_idx[line[name_to_column["_geom_bond_atom_site_label_1"]]] + atom_two_idx = label_num_to_idx[line[name_to_column["_geom_bond_atom_site_label_2"]]] + @printf("Connecting: %d %d\n", atom_one_idx, atom_two_idx) + add_edge!(bonds, atom_one_idx, atom_two_idx) - i += 1 - loop_starts = i - while length(split(lines[i], [''', ' ', '\t', ','], keepempty=false)) == 1 - name_to_column[split(lines[i])[1]] = i + 1 - loop_starts # iterate to next line in file i += 1 end + @printf("Total Number of bonds read: %d\nTotal Number of vertices: %d\n", ne(bonds), nv(bonds)) - @assert haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") "Need column name `_symmetry_equiv_pos_xyz` to parse symmetry information" - - symmetry_count = 0 - # CSD stores symmetry as one column in a string that ends - # up getting split on the spaces between commas (i.e. its - # not really one column) the length(name_to_column) + 2 - # should catch this hopefully there aren't other weird - # ways of writing cifs... - while i <= length(lines) && length(lines[i]) > 0 && lines[i][1] != '_' && !occursin("loop_", lines[i]) - symmetry_count += 1 - line = lines[i] - sym_funcs = split(line, [' ', ',', '''], keepempty=false) - - # store as strings so it can be written out later - new_sym_rule = Array{AbstractString, 1}(undef, 3) - - sym_start = name_to_column["_symmetry_equiv_pos_as_xyz"] - 1 - for j = 1:3 - new_sym_rule[j] = sym_funcs[j + sym_start] - end - - symmetry_rules = [symmetry_rules new_sym_rule] - - i += 1 - end - - @assert symmetry_count == size(symmetry_rules, 2) "number of symmetry rules must match the count" - - # finish reading in symmetry information, skip to next - # iteration of outer while-loop + # skip to next iteration in outer while loop continue - # read in keywords, store in a dictionary, then if - # `_atom_site_fract_.` or `_atom_site_Cartn_.` it will - # proceed to read in atom coordinate information - elseif ! atom_info - atom_column_name = "" - # name_to_column is a dictionary that e.g. returns which column contains x fractional coord - # use example: name_to_column["_atom_site_fract_x"] gives 3 - name_to_column = Dict{AbstractString, Int}() - - i += 1 - loop_starts = i - while length(split(lines[i])) == 1 && split(lines[i])[1][1] == '_' - if i == loop_starts - atom_column_name = split(lines[i])[1] - end - name_to_column[split(lines[i])[1]] = i + 1 - loop_starts - # iterate to next line in file - i += 1 - end - - # if the file provides fractional coordinates - fractional = haskey(name_to_column, "_atom_site_fract_x") && - haskey(name_to_column, "_atom_site_fract_y") && - haskey(name_to_column, "_atom_site_fract_z") - # if the file provides cartesian coordinates - cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && - haskey(name_to_column, "_atom_site_Cartn_y") && - haskey(name_to_column, "_atom_site_Cartn_z") && - ! fractional # if both are provided, will default - # to using fractional, so keep cartesian - # false - if fractional || cartesian - # found the atom_info, so don't need to check for it - # after reading in the information - atom_info = true - # read in atom_site info and store it in column based on - # the name_to_column dictionary - while i <= length(lines) && length(split(lines[i])) == length(name_to_column) - line = split(lines[i]) - - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - if fractional - coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), - mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] - elseif cartesian - coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), - parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), - parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] - else - error("The file does not store atom information in the form '_atom_site_fract_x' or '_atom_site_Cartn_x'") - end - # If charges present, import them - if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) - else - push!(charges_simple, 0.0) - end - # iterate to next line in file - i += 1 - end - - # finish reading in atom_site information, skip to next - # iteration of outer while-loop - # prevents skipping a line after finishing reading atoms - continue - end end -=# # ========================================================================== end # pick up unit cell lengths @@ -446,6 +355,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end if symmetry_info && convert_to_p1 + @assert ne(bonds) == 0 @sprintf("Cannot apply symmetry rules to a structure with bonds present. Do not use the argument `read_bonds_from_file` and insterad use `infer_bonds`once the structure is read in.") @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) # loop over all symmetry rules for i in 1:size(symmetry_rules, 2) @@ -517,7 +427,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, idx_nz = charge_values .!= 0.0 charges = Charges(charge_values[idx_nz], coords[:, idx_nz]) - framework = Framework(filename, box, atoms, charges; symmetry=symmetry_rules, space_group=space_group, is_p1=p1_symmetry) + framework = Framework(filename, box, atoms, charges; bonds=bonds, symmetry=symmetry_rules, space_group=space_group, is_p1=p1_symmetry) if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) From ce53c9a716be013a8b4716ae67609e4921eaeb10 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 29 Aug 2019 12:47:24 -0700 Subject: [PATCH 057/105] auto-wrap positions to the unit cell and provide keyword argument to prevent this --- src/Crystal.jl | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 1c13652ca..f0dd8855c 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -17,7 +17,8 @@ Framework(name::String, box::Box, atoms::Atoms, Charges::Charges) = Framework( """ framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, - remove_overlap=false) + remove_overlap=false, convert_to_p1=true, + wrap_to_unit_cell=true) framework = Framework(name, box, atoms, charges, symmetry, space_group, is_p1) framework = Framework(name, box, atoms, charges) @@ -51,10 +52,13 @@ function it is assumed it is in P1 symmetry. so that it can be written out again in the write_cif function - `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This is used before any simulations such as GCMC and Henry Coefficient +- `wrap_to_unit_cell::Bool`: Whether the atom and charge positions will be + wrapped to the unit cell. """ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, - remove_overlap::Bool=false, convert_to_p1::Bool=true) + remove_overlap::Bool=false, convert_to_p1::Bool=true, + wrap_to_unit_cell::Bool=true) # Read file extension. Ensure we can read the file type extension = split(filename, ".")[end] if ! (extension in ["cif", "cssr"]) @@ -357,6 +361,13 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, framework = Framework(filename, box, atoms, charges, symmetry_rules, space_group, p1_symmetry) + if wrap_to_unit_cell + framework.atoms.xf .= mod.(framework.atoms.xf, 1.0) + framework.charges.xf .= mod.(frameworks.charges.xf, 1.0) + end + strip_numbers_from_atom_labels!(framework) + + if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) error(@sprintf("Framework %s is not charge neutral; net charge is %f e. Ignore @@ -366,8 +377,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end end - strip_numbers_from_atom_labels!(framework) - if remove_overlap return remove_overlapping_atoms_and_charges(framework) end From f3addd0f917280431d4cd44e6b9af1424efa940e Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 29 Aug 2019 13:56:19 -0700 Subject: [PATCH 058/105] update testing files for symmetry reader. there don't require a remove overlap --- src/Crystal.jl | 2 +- test/crystal_test.jl | 22 +- test/data/crystals/ORIVOC_clean.cif | 54 --- test/data/crystals/ORIVOC_clean_P1.cif | 189 --------- test/data/crystals/ORIVOC_clean_fract.cif | 55 --- .../data/crystals/symmetry_test_structure.cif | 393 ++++++++++++++++++ .../crystals/symmetry_test_structure_P1.cif | 291 +++++++++++++ .../symmetry_test_structure_cartn.cif | 53 +++ 8 files changed, 749 insertions(+), 310 deletions(-) delete mode 100644 test/data/crystals/ORIVOC_clean.cif delete mode 100644 test/data/crystals/ORIVOC_clean_P1.cif delete mode 100644 test/data/crystals/ORIVOC_clean_fract.cif create mode 100644 test/data/crystals/symmetry_test_structure.cif create mode 100644 test/data/crystals/symmetry_test_structure_P1.cif create mode 100644 test/data/crystals/symmetry_test_structure_cartn.cif diff --git a/src/Crystal.jl b/src/Crystal.jl index f0dd8855c..9ab4ae385 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -363,7 +363,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, if wrap_to_unit_cell framework.atoms.xf .= mod.(framework.atoms.xf, 1.0) - framework.charges.xf .= mod.(frameworks.charges.xf, 1.0) + framework.charges.xf .= mod.(framework.charges.xf, 1.0) end strip_numbers_from_atom_labels!(framework) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 72280d248..612129b91 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -41,9 +41,9 @@ using Random # no atoms should overlap # should place atoms in the same positions as the P1 conversion using # openBabel - non_P1_framework = Framework("ORIVOC_clean_fract.cif", remove_overlap=true) - non_P1_cartesian = Framework("ORIVOC_clean.cif", remove_overlap=true) - P1_framework = Framework("ORIVOC_clean_P1.cif", remove_overlap=true) + non_P1_framework = Framework("symmetry_test_structure.cif") + non_P1_cartesian = Framework("symmetry_test_structure_cartn.cif") + P1_framework = Framework("symmetry_test_structure_P1.cif") # wrap all atoms and charges to be within the unit cell non_P1_framework.atoms.xf .= mod.(non_P1_framework.atoms.xf, 1.0) @@ -71,8 +71,8 @@ using Random # test reading in non-P1 then applying symmetry later # read in the same files as above, then convert to P1, then compare - non_P1_framework_symmetry = Framework("ORIVOC_clean_fract.cif", convert_to_p1=false) - non_P1_cartesian_symmetry = Framework("ORIVOC_clean.cif", convert_to_p1=false) + non_P1_framework_symmetry = Framework("symmetry_test_structure.cif", convert_to_p1=false) + non_P1_cartesian_symmetry = Framework("symmetry_test_structure_cartn.cif", convert_to_p1=false) # make sure these frameworks are not in P1 symmetry when convert_to_p1 is # set to false @@ -80,17 +80,17 @@ using Random @test ! non_P1_cartesian_symmetry.is_p1 # test write_cif in non_p1 symmetry - write_cif(non_P1_framework_symmetry, joinpath("data", "crystals", "rewritten_ORIVOC_clean_fract.cif")) + write_cif(non_P1_framework_symmetry, joinpath("data", "crystals", "rewritten_symmetry_test_structure.cif")) # keep this in cartesian to test both - write_cif(non_P1_cartesian_symmetry, joinpath("data", "crystals", "rewritten_ORIVOC_clean.cif"), fractional=false) - rewritten_non_p1_fractional = Framework("rewritten_ORIVOC_clean_fract.cif"; convert_to_p1=false) - rewritten_non_p1_cartesian = Framework("rewritten_ORIVOC_clean.cif"; convert_to_p1=false) + write_cif(non_P1_cartesian_symmetry, joinpath("data", "crystals", "rewritten_symmetry_test_structure_cartn.cif"), fractional=false) + rewritten_non_p1_fractional = Framework("rewritten_symmetry_test_structure.cif"; convert_to_p1=false) + rewritten_non_p1_cartesian = Framework("rewritten_symmetry_test_structure_cartn.cif"; convert_to_p1=false) @test isapprox(rewritten_non_p1_fractional, non_P1_framework_symmetry) @test isapprox(rewritten_non_p1_cartesian, non_P1_cartesian_symmetry) - non_P1_framework_symmetry = apply_symmetry_rules(non_P1_framework_symmetry, remove_overlap=true) - non_P1_cartesian_symmetry = apply_symmetry_rules(non_P1_cartesian_symmetry, remove_overlap=true) + non_P1_framework_symmetry = apply_symmetry_rules(non_P1_framework_symmetry) + non_P1_cartesian_symmetry = apply_symmetry_rules(non_P1_cartesian_symmetry) # wrap all atoms and charges to be within the unit cell non_P1_framework_symmetry.atoms.xf .= mod.(non_P1_framework_symmetry.atoms.xf, 1.0) diff --git a/test/data/crystals/ORIVOC_clean.cif b/test/data/crystals/ORIVOC_clean.cif deleted file mode 100644 index f1cf35577..000000000 --- a/test/data/crystals/ORIVOC_clean.cif +++ /dev/null @@ -1,54 +0,0 @@ -# CIF file generated by openbabel 2.3.2, see http://openbabel.sf.net -data_I -_chemical_name_common '' -_cell_length_a 26.179 -_cell_length_b 26.179 -_cell_length_c 6.65197 -_cell_angle_alpha 90 -_cell_angle_beta 90 -_cell_angle_gamma 120 -_symmetry_space_group_name_H-M 'R -3' -_space_group_name_Hall '-R 3' -loop_ - _symmetry_equiv_pos_as_xyz - 'x,y,z' - '-y,x-y,z' - '-x+y,-x,z' - '-x,-y,-z' - 'y,-x+y,-z' - 'x-y,x,-z' - '2/3+x,1/3+y,1/3+z' - '2/3-y,1/3+x-y,1/3+z' - '2/3-x+y,1/3-x,1/3+z' - '2/3-x,1/3-y,1/3-z' - '2/3+y,1/3-x+y,1/3-z' - '2/3+x-y,1/3+x,1/3-z' - '1/3+x,2/3+y,2/3+z' - '1/3-y,2/3+x-y,2/3+z' - '1/3-x+y,2/3-x,2/3+z' - '1/3-x,2/3-y,2/3-z' - '1/3+y,2/3-x+y,2/3-z' - '1/3+x-y,2/3+x,2/3-z' -loop_ - _atom_site_type_symbol - _atom_site_label - _atom_site_Cartn_x - _atom_site_Cartn_y - _atom_site_Cartn_z - Zn Zn1 5.56460 7.95050 1.01183 - C C2 5.78477 4.62184 1.91437 - C C3 6.09211 4.99683 0.60959 - C C4 6.85235 4.15345 -0.19610 - C C5 7.30459 2.93530 0.30293 - C C6 6.99711 2.56054 1.60771 - C C7 6.23727 3.40370 2.41347 - H H8 7.09097 4.44478 -1.20953 - H H9 5.99852 3.11259 3.42683 - C C10 4.96746 5.52871 2.78086 - O O11 4.55959 6.62692 2.33085 - O O12 4.69035 5.19067 3.95726 - C C13 8.12176 2.02866 -0.56349 - O O14 8.52963 0.93090 -0.11441 - O O15 8.39913 2.36669 -1.73996 - O O16 5.65426 6.17644 0.12639 - O O17 7.43535 1.38070 2.09138 diff --git a/test/data/crystals/ORIVOC_clean_P1.cif b/test/data/crystals/ORIVOC_clean_P1.cif deleted file mode 100644 index 26cca6420..000000000 --- a/test/data/crystals/ORIVOC_clean_P1.cif +++ /dev/null @@ -1,189 +0,0 @@ -# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net -data_I -_chemical_name_common '' -_cell_length_a 26.179 -_cell_length_b 26.179 -_cell_length_c 6.65197 -_cell_angle_alpha 90 -_cell_angle_beta 90 -_cell_angle_gamma 120 -_space_group_name_H-M_alt 'P 1' -_space_group_name_Hall 'P 1' -_symmetry_space_group_name_H-M 'P 1' -loop_ - _symmetry_equiv_pos_as_xyz - x,y,z -loop_ - _atom_site_label - _atom_site_type_symbol - _atom_site_fract_x - _atom_site_fract_y - _atom_site_fract_z - _atom_site_occupancy - Zn1 Zn 0.38790 0.35068 0.15211 1.000 - C2 C 0.32290 0.20386 0.28779 1.000 - C3 C 0.34291 0.22040 0.09164 1.000 - C4 C 0.35335 0.18320 0.97052 1.000 - C5 C 0.34376 0.12947 0.04554 1.000 - C6 C 0.32375 0.11294 0.24169 1.000 - C7 C 0.31332 0.15013 0.36282 1.000 - H8 H 0.36889 0.19605 0.81817 1.000 - H9 H 0.29778 0.13729 0.51516 1.000 - C10 C 0.31168 0.24386 0.41805 1.000 - O11 O 0.32032 0.29230 0.35040 1.000 - O12 O 0.29364 0.22895 0.59490 1.000 - C13 C 0.35498 0.08948 0.91529 1.000 - O14 O 0.34635 0.04106 0.98280 1.000 - O15 O 0.37303 0.10439 0.73843 1.000 - O16 O 0.35220 0.27243 0.01900 1.000 - O17 O 0.31447 0.06090 0.31440 1.000 - Zn1 Zn 0.64932 0.03722 0.15211 1.000 - Zn1 Zn 0.96278 0.61210 0.15211 1.000 - Zn1 Zn 0.61210 0.64932 0.84789 1.000 - Zn1 Zn 0.35068 0.96278 0.84789 1.000 - Zn1 Zn 0.03722 0.38790 0.84789 1.000 - Zn1 Zn 0.05457 0.68401 0.48544 1.000 - Zn1 Zn 0.31599 0.37055 0.48544 1.000 - Zn1 Zn 0.62945 0.94543 0.48544 1.000 - Zn1 Zn 0.27877 0.98265 0.18122 1.000 - Zn1 Zn 0.01735 0.29611 0.18122 1.000 - Zn1 Zn 0.70389 0.72123 0.18122 1.000 - Zn1 Zn 0.72123 0.01735 0.81878 1.000 - Zn1 Zn 0.98265 0.70389 0.81878 1.000 - Zn1 Zn 0.29611 0.27877 0.81878 1.000 - Zn1 Zn 0.94543 0.31599 0.51456 1.000 - Zn1 Zn 0.68401 0.62945 0.51456 1.000 - Zn1 Zn 0.37055 0.05457 0.51456 1.000 - C2 C 0.79614 0.11904 0.28779 1.000 - C2 C 0.88096 0.67710 0.28779 1.000 - C2 C 0.67710 0.79614 0.71221 1.000 - C2 C 0.20386 0.88096 0.71221 1.000 - C2 C 0.11904 0.32290 0.71221 1.000 - C2 C 0.98957 0.53719 0.62112 1.000 - C2 C 0.46281 0.45237 0.62112 1.000 - C2 C 0.54763 0.01043 0.62112 1.000 - C2 C 0.87053 0.21429 0.04554 1.000 - C2 C 0.78571 0.65623 0.04554 1.000 - C2 C 0.65623 0.87053 0.95446 1.000 - C2 C 0.12947 0.78571 0.95446 1.000 - C2 C 0.21429 0.34377 0.95446 1.000 - C2 C 0.01043 0.46281 0.37888 1.000 - C2 C 0.53719 0.54763 0.37888 1.000 - C2 C 0.45237 0.98957 0.37888 1.000 - C3 C 0.77960 0.12251 0.09164 1.000 - C3 C 0.87749 0.65709 0.09164 1.000 - C3 C 0.65709 0.77960 0.90836 1.000 - C3 C 0.22040 0.87749 0.90836 1.000 - C3 C 0.12251 0.34291 0.90836 1.000 - C3 C 0.00958 0.55373 0.42497 1.000 - C3 C 0.44627 0.45584 0.42497 1.000 - C3 C 0.54416 0.99042 0.42497 1.000 - C3 C 0.88707 0.21082 0.24169 1.000 - C3 C 0.78918 0.67624 0.24169 1.000 - C3 C 0.67624 0.88707 0.75831 1.000 - C3 C 0.11293 0.78918 0.75831 1.000 - C3 C 0.21082 0.32376 0.75831 1.000 - C3 C 0.99042 0.44627 0.57503 1.000 - C3 C 0.55373 0.54416 0.57503 1.000 - C3 C 0.45584 0.00958 0.57503 1.000 - C4 C 0.81680 0.17015 0.97052 1.000 - C4 C 0.82985 0.64665 0.97052 1.000 - C4 C 0.64665 0.81680 0.02948 1.000 - C4 C 0.18320 0.82985 0.02948 1.000 - C4 C 0.17015 0.35335 0.02948 1.000 - C4 C 0.02002 0.51653 0.30385 1.000 - C4 C 0.48347 0.50348 0.30385 1.000 - C4 C 0.49652 0.97998 0.30385 1.000 - C4 C 0.84987 0.16318 0.36281 1.000 - C4 C 0.83682 0.68668 0.36281 1.000 - C4 C 0.68668 0.84987 0.63719 1.000 - C4 C 0.15013 0.83682 0.63719 1.000 - C4 C 0.16318 0.31332 0.63719 1.000 - C4 C 0.97998 0.48347 0.69615 1.000 - C4 C 0.51653 0.49652 0.69615 1.000 - C4 C 0.50348 0.02002 0.69615 1.000 - C7 C 0.35335 0.18320 0.97051 1.000 - H8 H 0.80395 0.17284 0.81817 1.000 - H8 H 0.82716 0.63111 0.81817 1.000 - H8 H 0.63111 0.80395 0.18183 1.000 - H8 H 0.19605 0.82716 0.18183 1.000 - H8 H 0.17284 0.36889 0.18183 1.000 - H8 H 0.03556 0.52938 0.15150 1.000 - H8 H 0.47062 0.50617 0.15150 1.000 - H8 H 0.49383 0.96444 0.15150 1.000 - H8 H 0.86272 0.16049 0.51516 1.000 - H8 H 0.83951 0.70222 0.51516 1.000 - H8 H 0.70222 0.86272 0.48484 1.000 - H8 H 0.13728 0.83951 0.48484 1.000 - H8 H 0.16049 0.29778 0.48484 1.000 - H8 H 0.96444 0.47062 0.84850 1.000 - H8 H 0.52938 0.49383 0.84850 1.000 - H8 H 0.50617 0.03556 0.84850 1.000 - H9 H 0.36889 0.19604 0.81817 1.000 - C10 C 0.75614 0.06782 0.41805 1.000 - C10 C 0.93218 0.68832 0.41805 1.000 - C10 C 0.68832 0.75614 0.58195 1.000 - C10 C 0.24386 0.93218 0.58195 1.000 - C10 C 0.06782 0.31168 0.58195 1.000 - C10 C 0.97835 0.57719 0.75138 1.000 - C10 C 0.42281 0.40115 0.75138 1.000 - C10 C 0.59885 0.02165 0.75138 1.000 - C10 C 0.35499 0.08947 0.91528 1.000 - C10 C 0.91053 0.26551 0.91528 1.000 - C10 C 0.73449 0.64501 0.91528 1.000 - C10 C 0.64501 0.91053 0.08472 1.000 - C10 C 0.08947 0.73449 0.08472 1.000 - C10 C 0.26551 0.35499 0.08472 1.000 - C10 C 0.02165 0.42281 0.24862 1.000 - C10 C 0.57719 0.59885 0.24862 1.000 - C10 C 0.40115 0.97835 0.24862 1.000 - O11 O 0.70770 0.02802 0.35040 1.000 - O11 O 0.97198 0.67968 0.35040 1.000 - O11 O 0.67968 0.70770 0.64960 1.000 - O11 O 0.29230 0.97198 0.64960 1.000 - O11 O 0.02802 0.32032 0.64960 1.000 - O11 O 0.98699 0.62563 0.68373 1.000 - O11 O 0.37437 0.36135 0.68373 1.000 - O11 O 0.63865 0.01301 0.68373 1.000 - O11 O 0.34635 0.04103 0.98293 1.000 - O11 O 0.95897 0.30531 0.98293 1.000 - O11 O 0.69469 0.65365 0.98293 1.000 - O11 O 0.65365 0.95897 0.01707 1.000 - O11 O 0.04103 0.69469 0.01707 1.000 - O11 O 0.30531 0.34635 0.01707 1.000 - O11 O 0.01301 0.37437 0.31627 1.000 - O11 O 0.62563 0.63865 0.31627 1.000 - O11 O 0.36135 0.98699 0.31627 1.000 - O12 O 0.77105 0.06469 0.59490 1.000 - O12 O 0.93531 0.70636 0.59490 1.000 - O12 O 0.70636 0.77105 0.40510 1.000 - O12 O 0.22895 0.93531 0.40510 1.000 - O12 O 0.06469 0.29364 0.40510 1.000 - O12 O 0.96031 0.56228 0.92823 1.000 - O12 O 0.43772 0.39802 0.92823 1.000 - O12 O 0.60198 0.03969 0.92823 1.000 - O12 O 0.37303 0.10438 0.73843 1.000 - O12 O 0.89562 0.26864 0.73843 1.000 - O12 O 0.73136 0.62697 0.73843 1.000 - O12 O 0.62697 0.89562 0.26157 1.000 - O12 O 0.10438 0.73136 0.26157 1.000 - O12 O 0.26864 0.37303 0.26157 1.000 - O12 O 0.03969 0.43772 0.07177 1.000 - O12 O 0.56228 0.60198 0.07177 1.000 - O12 O 0.39802 0.96031 0.07177 1.000 - O16 O 0.72757 0.07977 0.01900 1.000 - O16 O 0.92023 0.64780 0.01900 1.000 - O16 O 0.64780 0.72757 0.98100 1.000 - O16 O 0.27243 0.92023 0.98100 1.000 - O16 O 0.07977 0.35220 0.98100 1.000 - O16 O 0.01887 0.60576 0.35233 1.000 - O16 O 0.39424 0.41310 0.35233 1.000 - O16 O 0.58690 0.98113 0.35233 1.000 - O16 O 0.93910 0.25356 0.31433 1.000 - O16 O 0.74644 0.68553 0.31433 1.000 - O16 O 0.68553 0.93910 0.68567 1.000 - O16 O 0.06090 0.74644 0.68567 1.000 - O16 O 0.25356 0.31447 0.68567 1.000 - O16 O 0.98113 0.39424 0.64767 1.000 - O16 O 0.60576 0.58690 0.64767 1.000 - O16 O 0.41310 0.01887 0.64767 1.000 diff --git a/test/data/crystals/ORIVOC_clean_fract.cif b/test/data/crystals/ORIVOC_clean_fract.cif deleted file mode 100644 index 1851566ea..000000000 --- a/test/data/crystals/ORIVOC_clean_fract.cif +++ /dev/null @@ -1,55 +0,0 @@ -# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net -data_I -_chemical_name_common '' -_cell_length_a 26.179 -_cell_length_b 26.179 -_cell_length_c 6.65197 -_cell_angle_alpha 90 -_cell_angle_beta 90 -_cell_angle_gamma 120 -_symmetry_space_group_name_H-M 'R -3' -_space_group_name_Hall '-R 3' -loop_ - _symmetry_equiv_pos_as_xyz - x,y,z - -y,x-y,z - -x+y,-x,z - -x,-y,-z - y,-x+y,-z - x-y,x,-z - 2/3+x,1/3+y,1/3+z - 2/3-y,1/3+x-y,1/3+z - 2/3-x+y,1/3-x,1/3+z - 2/3-x,1/3-y,1/3-z - 2/3+y,1/3-x+y,1/3-z - 2/3+x-y,1/3+x,1/3-z - 1/3+x,2/3+y,2/3+z - 1/3-y,2/3+x-y,2/3+z - 1/3-x+y,2/3-x,2/3+z - 1/3-x,2/3-y,2/3-z - 1/3+y,2/3-x+y,2/3-z - 1/3+x-y,2/3+x,2/3-z -loop_ - _atom_site_label - _atom_site_type_symbol - _atom_site_fract_x - _atom_site_fract_y - _atom_site_fract_z - _atom_site_occupancy - Zn1 Zn 0.38790 0.35068 0.15211 1.000 - C2 C 0.32290 0.20386 0.28779 1.000 - C3 C 0.34291 0.22040 0.09164 1.000 - C4 C 0.35335 0.18320 0.97052 1.000 - C5 C 0.34376 0.12947 0.04554 1.000 - C6 C 0.32375 0.11294 0.24169 1.000 - C7 C 0.31332 0.15013 0.36282 1.000 - H8 H 0.36889 0.19605 0.81817 1.000 - H9 H 0.29778 0.13729 0.51516 1.000 - C10 C 0.31168 0.24386 0.41805 1.000 - O11 O 0.32032 0.29230 0.35040 1.000 - O12 O 0.29364 0.22895 0.59490 1.000 - C13 C 0.35498 0.08948 0.91529 1.000 - O14 O 0.34635 0.04106 0.98280 1.000 - O15 O 0.37303 0.10439 0.73843 1.000 - O16 O 0.35220 0.27243 0.01900 1.000 - O17 O 0.31447 0.06090 0.31440 1.000 diff --git a/test/data/crystals/symmetry_test_structure.cif b/test/data/crystals/symmetry_test_structure.cif new file mode 100644 index 000000000..abd2caf8a --- /dev/null +++ b/test/data/crystals/symmetry_test_structure.cif @@ -0,0 +1,393 @@ +data_2P0_publ + +_pd_block_id + 2011-01-13T16:00|FE-B||Overall + +_audit_creation_method "from EXP file using GSAS2CIF" +_audit_creation_date 2011-01-13T16:00 +_audit_author_name "" +_audit_update_record +; 2011-01-13T16:00 Initial CIF as created by GSAS2CIF +; + +#============================================================================= +# this information describes the project, paper etc. for the CIF # +# Acta Cryst. Section C papers and editorial correspondence is generated # +# from the information in this section # +# # +# (from) CIF submission form for Rietveld refinements (Acta Cryst. C) # +# Version 14 December 1998 # +#============================================================================= +# 1. SUBMISSION DETAILS + +_publ_contact_author_name ? # Name of author for correspondence +_publ_contact_author_address # Address of author for correspondence +; ? +; +_publ_contact_author_email ? +_publ_contact_author_fax ? +_publ_contact_author_phone ? + +_publ_contact_letter +; ? +; + +_publ_requested_journal ? +_publ_requested_coeditor_name ? +_publ_requested_category ? # Acta C: one of CI/CM/CO/FI/FM/FO + +#============================================================================== + +# 2. PROCESSING SUMMARY (IUCr Office Use Only) + +_journal_data_validation_number ? + +_journal_date_recd_electronic ? +_journal_date_to_coeditor ? +_journal_date_from_coeditor ? +_journal_date_accepted ? +_journal_date_printers_first ? +_journal_date_printers_final ? +_journal_date_proofs_out ? +_journal_date_proofs_in ? +_journal_coeditor_name ? +_journal_coeditor_code ? +_journal_coeditor_notes +; ? +; +_journal_techeditor_code ? +_journal_techeditor_notes +; ? +; +_journal_coden_ASTM ? +_journal_name_full ? +_journal_year ? +_journal_volume ? +_journal_issue ? +_journal_page_first ? +_journal_page_last ? +_journal_paper_category ? +_journal_suppl_publ_number ? +_journal_suppl_publ_pages ? + +#============================================================================== + +# 3. TITLE AND AUTHOR LIST + +_publ_section_title +; ? +; +_publ_section_title_footnote +; ? +; + +# The loop structure below should contain the names and addresses of all +# authors, in the required order of publication. Repeat as necessary. + +loop_ + _publ_author_name + _publ_author_footnote + _publ_author_address + ? #<--'Last name, first name' +; ? +; +; ? +; + +#============================================================================== + +# 4. TEXT + +_publ_section_synopsis +; ? +; +_publ_section_abstract +; ? +; +_publ_section_comment +; ? +; +_publ_section_exptl_prep # Details of the preparation of the sample(s) + # should be given here. +; ? +; +_publ_section_exptl_refinement +; ? +; +_publ_section_references +; ? +; +_publ_section_figure_captions +; ? +; +_publ_section_acknowledgements +; ? +; + +#============================================================================= +# 5. OVERALL REFINEMENT & COMPUTING DETAILS + +_refine_special_details +; ? +; +_pd_proc_ls_special_details +; ? +; + +# The following items are used to identify the programs used. +_computing_molecular_graphics ? +_computing_publication_material ? + +_refine_ls_weighting_scheme ? +_refine_ls_weighting_details ? +_refine_ls_hydrogen_treatment ? +_refine_ls_extinction_method ? +_refine_ls_extinction_coef ? +_refine_ls_number_constraints ? + +_refine_ls_restrained_S_all ? +_refine_ls_restrained_S_obs ? + +#============================================================================== +# 6. SAMPLE PREPARATION DATA + +# (In the unusual case where multiple samples are used in a single +# Rietveld study, this information should be moved into the phase +# blocks) + +# The following three fields describe the preparation of the material. +# The cooling rate is in K/min. The pressure at which the sample was +# prepared is in kPa. The temperature of preparation is in K. + +_pd_prep_cool_rate ? +_pd_prep_pressure ? +_pd_prep_temperature ? + +_pd_char_colour ? # use ICDD colour descriptions +data_FE-B_overall + +_refine_ls_shift/su_max 0.27 +_refine_ls_shift/su_mean 0.11 +_computing_structure_refinement GSAS +_refine_ls_number_parameters 45 +_refine_ls_goodness_of_fit_all 0.98 +_refine_ls_number_restraints 2 +_refine_ls_matrix_type full + +# pointers to the phase blocks +loop_ _pd_phase_block_id + 2011-01-13T16:00|FE-B_phase1||| +# pointers to the diffraction patterns +loop_ _pd_block_diffractogram_id + ? + +# Information for phase 1 +data_FE-B_phase_1 + +_pd_block_id + 2011-01-13T16:00|FE-B_phase1||| + +#============================================================================== +# 7. CHEMICAL, STRUCTURAL AND CRYSTAL DATA + +_pd_char_particle_morphology ? + +_chemical_name_systematic +; ? +; +_chemical_name_common ? +_chemical_formula_moiety ? +_chemical_formula_structural ? +_chemical_formula_analytical ? +_chemical_melting_point ? +_chemical_compound_source ? # for minerals and + # natural products +_symmetry_space_group_name_Hall ? + +_exptl_crystal_F_000 ? +_exptl_crystal_density_diffrn ? +_exptl_crystal_density_meas ? +_exptl_crystal_density_method ? + +_cell_measurement_temperature ? + +_cell_special_details +; ? +; + +_geom_special_details ? + +# The following item identifies the program(s) used (if appropriate). +_computing_structure_solution ? + +#============================================================================== + +# 8. Phase information from GSAS + +_pd_phase_name + "from /data/people/craigy/MgMOF/BT1/1108/MOF74.xtl" +_cell_length_a 25.5177(9) +_cell_length_b 25.5177 +_cell_length_c 6.9661(4) +_cell_angle_alpha 90.0 +_cell_angle_beta 90.0 +_cell_angle_gamma 120.0 +_cell_volume 3928.31(24) +_symmetry_cell_setting trigonal +_symmetry_space_group_name_H-M "R -3" +loop_ +_symmetry_equiv_pos_site_id +_symmetry_equiv_pos_as_xyz + 1 +x,+y,+z + 2 -y,x-y,+z + 3 y-x,-x,+z + -1 -x,-y,-z + -2 +y,y-x,-z + -3 x-y,+x,-z + 101 +x+1/3,+y+2/3,+z+2/3 + 102 -y+1/3,x-y+2/3,+z+2/3 + 103 y-x+1/3,-x+2/3,+z+2/3 + -101 -x+2/3,-y+1/3,-z+1/3 + -102 +y+2/3,y-x+1/3,-z+1/3 + -103 x-y+2/3,+x+1/3,-z+1/3 + 201 +x+2/3,+y+1/3,+z+1/3 + 202 -y+2/3,x-y+1/3,+z+1/3 + 203 y-x+2/3,-x+1/3,+z+1/3 + -201 -x+1/3,-y+2/3,-z+2/3 + -202 +y+1/3,y-x+2/3,-z+2/3 + -203 x-y+1/3,+x+2/3,-z+2/3 + +# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS + + +loop_ + _atom_site_type_symbol + _atom_site_label + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + _atom_site_thermal_displace_type + _atom_site_U_iso_or_equiv + _atom_site_symmetry_multiplicity +Fe Fe 0.38873(29) 0.35262(31) 0.1477(10) 1.0 Uiso 0.0093 18 +O O1 0.3182(6) 0.2951(5) 0.3500(16) 1.0 Uiso 0.01896 18 +O O2 0.3068(5) 0.2318(6) 0.5894(17) 1.0 Uiso 0.0267 18 +O O3 0.3545(5) 0.2758(5) 0.0009(16) 1.0 Uiso 0.00934 18 +C C1 0.3189(5) 0.2489(5) 0.4144(13) 1.0 Uiso 0.00914 18 +C C2 0.3276(5) 0.2071(5) 0.2780(16) 1.0 Uiso 0.00989 18 +C C3 0.3444(5) 0.2224(5) 0.0839(15) 1.0 Uiso 0.0201 18 +C C4 0.3512(4) 0.1806(5) -0.0277(12) 1.0 Uiso 0.01662 18 +H H 0.3596(9) 0.1861(8) -0.1755(22) 1.0 Uiso 0.01289 18 +O O1a 0.5591(6) 0.6699(6) 0.6775(20) 0.901(8) Uiso 0.01063 18 +O O1b 0.5236(6) 0.6317(6) 0.7955(17) 0.901(8) Uiso 0.01161 18 +O O2a 0.5134(6) 0.6747(7) 0.2470(20) 0.866(9) Uiso 0.03843 18 +O O2b 0.5295(7) 0.7244(6) 0.3087(26) 0.866(9) Uiso 0.06115 18 +O O3a 0.059(4) 0.1362(25) 0.967(11) 0.221(9) Uiso 0.05492 18 +O O3b 0.081(13) 0.183(5) 0.879(28) 0.221(9) Uiso 0.44935 18 + +loop_ _atom_type_symbol + _atom_type_number_in_cell + Fe 18.0 + O 125.32 + C 72.0 + H 18.0 + +# If you change Z, be sure to change all 3 of the following +_chemical_formula_sum "C4 H Fe O6.96" +_chemical_formula_weight 216.29 +_cell_formula_units_Z 18 + +# MOLECULAR GEOMETRY + +loop_ + _geom_bond_atom_site_label_1 + _geom_bond_atom_site_label_2 + _geom_bond_distance + _geom_bond_site_symmetry_1 + _geom_bond_site_symmetry_2 + _geom_bond_publ_flag + Fe O1 2.177(13) . 1_555 N + Fe O1 2.155(12) . 103_554 N + Fe O2 2.047(14) . 202_554 N + Fe O3 1.985(12) . 1_555 N + Fe O3 1.978(12) . 202_555 N + Fe O1a 2.087(15) . -1_666 N + Fe O1b 2.103(14) . -1_666 N + O1 Fe 2.177(13) . 1_555 N + O1 Fe 2.155(12) . 202_555 N + O1 C1 1.269(13) . 1_555 N + O2 Fe 2.047(14) . 103_555 N + O2 C1 1.280(12) . 1_555 N + O3 Fe 1.985(12) . 1_555 N + O3 Fe 1.978(12) . 103_554 N + O3 C3 1.380(17) . 1_555 N + C1 O1 1.269(13) . 1_555 N + C1 O2 1.280(12) . 1_555 N + C1 C2 1.525(13) . 1_555 N + C2 C1 1.525(13) . 1_555 N + C2 C3 1.413(12) . 1_555 N + C2 C4 1.389(14) . -201_444 N + C2 H 2.098(19) . -201_444 N + C3 O3 1.380(17) . 1_555 N + C3 C2 1.413(12) . 1_555 N + C3 C4 1.400(14) . 1_555 N + C4 C2 1.389(14) . -201_444 N + C4 C3 1.400(14) . 1_555 N + C4 H 1.047(17) . 1_555 N + H C2 2.098(19) . -201_444 N + H C4 1.047(17) . 1_555 N + O1a Fe 2.087(15) . -1_666 N + O1a O1b 1.250(16) . 1_555 N + O1b Fe 2.103(14) . -1_666 N + O1b O1a 1.250(16) . 1_555 N + O2a O2b 1.1999(10) . 1_555 N + O2b O2a 1.1999(10) . 1_555 N + O3a O3b 1.201(4) . 1_555 N + O3b O3a 1.201(4) . 1_555 N + +loop_ + _geom_angle_atom_site_label_1 + _geom_angle_atom_site_label_2 + _geom_angle_atom_site_label_3 + _geom_angle + _geom_angle_site_symmetry_1 + _geom_angle_site_symmetry_2 + _geom_angle_site_symmetry_3 + _geom_angle_publ_flag + O1 Fe O2 84.7(5) 103_554 . 202_554 N + O1 Fe O3 78.2(4) 103_554 . 1_555 N + O1 Fe O3 89.1(6) 103_554 . 202_555 N + O1 Fe O1a 160.0(6) 103_554 . -1_666 N + O1 Fe O1b 164.3(5) 103_554 . -1_666 N + O2 Fe O3 96.2(5) 202_554 . 1_555 N + O2 Fe O3 100.6(6) 202_554 . 202_555 N + O2 Fe O1a 114.4(6) 202_554 . -1_666 N + O2 Fe O1b 79.9(5) 202_554 . -1_666 N + O3 Fe O3 157.9(6) 1_555 . 202_555 N + O3 Fe O1a 93.1(6) 1_555 . -1_666 N + O3 Fe O1b 100.7(6) 1_555 . -1_666 N + O3 Fe O1a 92.9(5) 202_555 . -1_666 N + O3 Fe O1b 96.4(6) 202_555 . -1_666 N + O1a Fe O1b 34.7(4) -1_666 . -1_666 N + Fe O1 C1 134.2(10) 202_555 . 1_555 N + Fe O2 C1 127.6(9) 103_555 . 1_555 N + Fe O3 Fe 106.1(7) 1_555 . 103_554 N + Fe O3 C3 122.1(9) 1_555 . 1_555 N + Fe O3 C3 121.8(9) 103_554 . 1_555 N + O1 C1 O2 122.8(12) 1_555 . 1_555 N + O1 C1 C2 120.2(11) 1_555 . 1_555 N + O2 C1 C2 116.7(10) 1_555 . 1_555 N + C1 C2 C3 122.3(11) 1_555 . 1_555 N + C1 C2 C4 113.7(9) 1_555 . -201_555 N + C3 C2 C4 124.0(10) 1_555 . -201_555 N + O3 C3 C2 123.7(11) 1_555 . 1_555 N + O3 C3 C4 119.1(10) 1_555 . 1_555 N + C2 C3 C4 117.2(11) 1_555 . 1_555 N + C2 C4 C3 118.7(8) -201_555 . 1_555 N + C2 C4 H 118.3(13) -201_555 . 1_555 N + C3 C4 H 122.6(14) 1_555 . 1_555 N + Fe O1a O1b 73.4(9) -1_666 . 1_555 N + Fe O1b O1a 71.9(8) -1_666 . 1_555 N +#--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--eof--# + diff --git a/test/data/crystals/symmetry_test_structure_P1.cif b/test/data/crystals/symmetry_test_structure_P1.cif new file mode 100644 index 000000000..5d4d9ffe4 --- /dev/null +++ b/test/data/crystals/symmetry_test_structure_P1.cif @@ -0,0 +1,291 @@ +# CIF file generated by openbabel 2.4.1, see http://openbabel.sf.net +data_I +_chemical_name_common '?' +_cell_length_a 25.5177 +_cell_length_b 25.5177 +_cell_length_c 6.9661 +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 120 +_space_group_name_H-M_alt 'P 1' +_space_group_name_Hall 'P 1' +loop_ + _symmetry_equiv_pos_as_xyz + x,y,z +loop_ + _atom_site_label + _atom_site_type_symbol + _atom_site_fract_x + _atom_site_fract_y + _atom_site_fract_z + _atom_site_occupancy + Fe Fe 0.38873 0.35262 0.14770 1.000 + O1 O 0.31820 0.29510 0.35000 1.000 + O2 O 0.30680 0.23180 0.58940 1.000 + O3 O 0.35450 0.27580 0.00090 1.000 + C1 C 0.31890 0.24890 0.41440 1.000 + C2 C 0.32760 0.20710 0.27800 1.000 + C3 C 0.34440 0.22240 0.08390 1.000 + C4 C 0.35120 0.18060 0.97230 1.000 + H H 0.35960 0.18610 0.82450 1.000 + O1a O 0.55910 0.66990 0.67750 0.901 + O1b O 0.52360 0.63170 0.79550 0.901 + O2a O 0.51340 0.67470 0.24700 0.866 + O2b O 0.52950 0.72440 0.30870 0.866 + O3a O 0.05900 0.13620 0.96700 0.221 + O3b O 0.08100 0.18300 0.87900 0.221 + Fe Fe 0.64738 0.03611 0.14770 1.000 + Fe Fe 0.96389 0.61127 0.14770 1.000 + Fe Fe 0.61127 0.64738 0.85230 1.000 + Fe Fe 0.35262 0.96389 0.85230 1.000 + Fe Fe 0.03611 0.38873 0.85230 1.000 + Fe Fe 0.05540 0.68595 0.48103 1.000 + Fe Fe 0.31405 0.36944 0.48103 1.000 + Fe Fe 0.63056 0.94460 0.48103 1.000 + Fe Fe 0.27794 0.98071 0.18563 1.000 + Fe Fe 0.01929 0.29722 0.18563 1.000 + Fe Fe 0.70278 0.72206 0.18563 1.000 + Fe Fe 0.72206 0.01929 0.81437 1.000 + Fe Fe 0.98071 0.70278 0.81437 1.000 + Fe Fe 0.29722 0.27794 0.81437 1.000 + Fe Fe 0.94460 0.31405 0.51897 1.000 + Fe Fe 0.68595 0.63056 0.51897 1.000 + Fe Fe 0.36944 0.05540 0.51897 1.000 + O1 O 0.70490 0.02310 0.35000 1.000 + O1 O 0.97690 0.68180 0.35000 1.000 + O1 O 0.68180 0.70490 0.65000 1.000 + O1 O 0.29510 0.97690 0.65000 1.000 + O1 O 0.02310 0.31820 0.65000 1.000 + O1 O 0.98487 0.62843 0.68333 1.000 + O1 O 0.37157 0.35643 0.68333 1.000 + O1 O 0.64357 0.01513 0.68333 1.000 + O1 O 0.34847 0.03823 0.98333 1.000 + O1 O 0.96177 0.31023 0.98333 1.000 + O1 O 0.68977 0.65153 0.98333 1.000 + O1 O 0.65153 0.96177 0.01667 1.000 + O1 O 0.03823 0.68977 0.01667 1.000 + O1 O 0.31023 0.34847 0.01667 1.000 + O1 O 0.01513 0.37157 0.31667 1.000 + O1 O 0.62843 0.64357 0.31667 1.000 + O1 O 0.35643 0.98487 0.31667 1.000 + O2 O 0.76820 0.07500 0.58940 1.000 + O2 O 0.92500 0.69320 0.58940 1.000 + O2 O 0.69320 0.76820 0.41060 1.000 + O2 O 0.23180 0.92500 0.41060 1.000 + O2 O 0.07500 0.30680 0.41060 1.000 + O2 O 0.97347 0.56513 0.92273 1.000 + O2 O 0.43487 0.40833 0.92273 1.000 + O2 O 0.59167 0.02653 0.92273 1.000 + O2 O 0.35987 0.10153 0.74393 1.000 + O2 O 0.89847 0.25833 0.74393 1.000 + O2 O 0.74167 0.64013 0.74393 1.000 + O2 O 0.64013 0.89847 0.25607 1.000 + O2 O 0.10153 0.74167 0.25607 1.000 + O2 O 0.25833 0.35987 0.25607 1.000 + O2 O 0.02653 0.43487 0.07727 1.000 + O2 O 0.56513 0.59167 0.07727 1.000 + O2 O 0.40833 0.97347 0.07727 1.000 + O3 O 0.72420 0.07870 0.00090 1.000 + O3 O 0.92130 0.64550 0.00090 1.000 + O3 O 0.64550 0.72420 0.99910 1.000 + O3 O 0.27580 0.92130 0.99910 1.000 + O3 O 0.07870 0.35450 0.99910 1.000 + O3 O 0.02117 0.60913 0.33423 1.000 + O3 O 0.39087 0.41203 0.33423 1.000 + O3 O 0.58797 0.97883 0.33423 1.000 + O3 O 0.31217 0.05753 0.33243 1.000 + O3 O 0.94247 0.25463 0.33243 1.000 + O3 O 0.74537 0.68783 0.33243 1.000 + O3 O 0.68783 0.94247 0.66757 1.000 + O3 O 0.05753 0.74537 0.66757 1.000 + O3 O 0.25463 0.31217 0.66757 1.000 + O3 O 0.97883 0.39087 0.66577 1.000 + O3 O 0.60913 0.58797 0.66577 1.000 + O3 O 0.41203 0.02117 0.66577 1.000 + C1 C 0.75110 0.07000 0.41440 1.000 + C1 C 0.93000 0.68110 0.41440 1.000 + C1 C 0.68110 0.75110 0.58560 1.000 + C1 C 0.24890 0.93000 0.58560 1.000 + C1 C 0.07000 0.31890 0.58560 1.000 + C1 C 0.98557 0.58223 0.74773 1.000 + C1 C 0.41777 0.40333 0.74773 1.000 + C1 C 0.59667 0.01443 0.74773 1.000 + C1 C 0.34777 0.08443 0.91893 1.000 + C1 C 0.91557 0.26333 0.91893 1.000 + C1 C 0.73667 0.65223 0.91893 1.000 + C1 C 0.65223 0.91557 0.08107 1.000 + C1 C 0.08443 0.73667 0.08107 1.000 + C1 C 0.26333 0.34777 0.08107 1.000 + C1 C 0.01443 0.41777 0.25227 1.000 + C1 C 0.58223 0.59667 0.25227 1.000 + C1 C 0.40333 0.98557 0.25227 1.000 + C2 C 0.79290 0.12050 0.27800 1.000 + C2 C 0.87950 0.67240 0.27800 1.000 + C2 C 0.67240 0.79290 0.72200 1.000 + C2 C 0.20710 0.87950 0.72200 1.000 + C2 C 0.12050 0.32760 0.72200 1.000 + C2 C 0.99427 0.54043 0.61133 1.000 + C2 C 0.45957 0.45383 0.61133 1.000 + C2 C 0.54617 0.00573 0.61133 1.000 + C2 C 0.33907 0.12623 0.05533 1.000 + C2 C 0.87377 0.21283 0.05533 1.000 + C2 C 0.78717 0.66093 0.05533 1.000 + C2 C 0.66093 0.87377 0.94467 1.000 + C2 C 0.12623 0.78717 0.94467 1.000 + C2 C 0.21283 0.33907 0.94467 1.000 + C2 C 0.00573 0.45957 0.38867 1.000 + C2 C 0.54043 0.54617 0.38867 1.000 + C2 C 0.45383 0.99427 0.38867 1.000 + C3 C 0.77760 0.12200 0.08390 1.000 + C3 C 0.87800 0.65560 0.08390 1.000 + C3 C 0.65560 0.77760 0.91610 1.000 + C3 C 0.22240 0.87800 0.91610 1.000 + C3 C 0.12200 0.34440 0.91610 1.000 + C3 C 0.01107 0.55573 0.41723 1.000 + C3 C 0.44427 0.45533 0.41723 1.000 + C3 C 0.54467 0.98893 0.41723 1.000 + C3 C 0.32227 0.11093 0.24943 1.000 + C3 C 0.88907 0.21133 0.24943 1.000 + C3 C 0.78867 0.67773 0.24943 1.000 + C3 C 0.67773 0.88907 0.75057 1.000 + C3 C 0.11093 0.78867 0.75057 1.000 + C3 C 0.21133 0.32227 0.75057 1.000 + C3 C 0.98893 0.44427 0.58277 1.000 + C3 C 0.55573 0.54467 0.58277 1.000 + C3 C 0.45533 0.01107 0.58277 1.000 + C4 C 0.81940 0.17060 0.97230 1.000 + C4 C 0.82940 0.64880 0.97230 1.000 + C4 C 0.64880 0.81940 0.02770 1.000 + C4 C 0.18060 0.82940 0.02770 1.000 + C4 C 0.17060 0.35120 0.02770 1.000 + C4 C 0.01787 0.51393 0.30563 1.000 + C4 C 0.48607 0.50393 0.30563 1.000 + C4 C 0.49607 0.98213 0.30563 1.000 + C4 C 0.31547 0.15273 0.36103 1.000 + C4 C 0.84727 0.16273 0.36103 1.000 + C4 C 0.83727 0.68453 0.36103 1.000 + C4 C 0.68453 0.84727 0.63897 1.000 + C4 C 0.15273 0.83727 0.63897 1.000 + C4 C 0.16273 0.31547 0.63897 1.000 + C4 C 0.98213 0.48607 0.69437 1.000 + C4 C 0.51393 0.49607 0.69437 1.000 + C4 C 0.50393 0.01787 0.69437 1.000 + H H 0.81390 0.17350 0.82450 1.000 + H H 0.82650 0.64040 0.82450 1.000 + H H 0.64040 0.81390 0.17550 1.000 + H H 0.18610 0.82650 0.17550 1.000 + H H 0.17350 0.35960 0.17550 1.000 + H H 0.02627 0.51943 0.15783 1.000 + H H 0.48057 0.50683 0.15783 1.000 + H H 0.49317 0.97373 0.15783 1.000 + H H 0.30707 0.14723 0.50883 1.000 + H H 0.85277 0.15983 0.50883 1.000 + H H 0.84017 0.69293 0.50883 1.000 + H H 0.69293 0.85277 0.49117 1.000 + H H 0.14723 0.84017 0.49117 1.000 + H H 0.15983 0.30707 0.49117 1.000 + H H 0.97373 0.48057 0.84217 1.000 + H H 0.51943 0.49317 0.84217 1.000 + H H 0.50683 0.02627 0.84217 1.000 + O1a O 0.33010 0.88920 0.67750 0.901 + O1a O 0.11080 0.44090 0.67750 0.901 + O1a O 0.44090 0.33010 0.32250 0.901 + O1a O 0.66990 0.11080 0.32250 0.901 + O1a O 0.88920 0.55910 0.32250 0.901 + O1a O 0.22577 0.00323 0.01083 0.901 + O1a O 0.99677 0.22253 0.01083 0.901 + O1a O 0.77747 0.77423 0.01083 0.901 + O1a O 0.10757 0.66343 0.65583 0.901 + O1a O 0.33657 0.44413 0.65583 0.901 + O1a O 0.55587 0.89243 0.65583 0.901 + O1a O 0.89243 0.33657 0.34417 0.901 + O1a O 0.66343 0.55587 0.34417 0.901 + O1a O 0.44413 0.10757 0.34417 0.901 + O1a O 0.77423 0.99677 0.98917 0.901 + O1a O 0.00323 0.77747 0.98917 0.901 + O1a O 0.22253 0.22577 0.98917 0.901 + O1b O 0.36830 0.89190 0.79550 0.901 + O1b O 0.10810 0.47640 0.79550 0.901 + O1b O 0.47640 0.36830 0.20450 0.901 + O1b O 0.63170 0.10810 0.20450 0.901 + O1b O 0.89190 0.52360 0.20450 0.901 + O1b O 0.19027 0.96503 0.12883 0.901 + O1b O 0.03497 0.22523 0.12883 0.901 + O1b O 0.77477 0.80973 0.12883 0.901 + O1b O 0.14307 0.70163 0.53783 0.901 + O1b O 0.29837 0.44143 0.53783 0.901 + O1b O 0.55857 0.85693 0.53783 0.901 + O1b O 0.85693 0.29837 0.46217 0.901 + O1b O 0.70163 0.55857 0.46217 0.901 + O1b O 0.44143 0.14307 0.46217 0.901 + O1b O 0.80973 0.03497 0.87117 0.901 + O1b O 0.96503 0.77477 0.87117 0.901 + O1b O 0.22523 0.19027 0.87117 0.901 + O2a O 0.32530 0.83870 0.24700 0.866 + O2a O 0.16130 0.48660 0.24700 0.866 + O2a O 0.48660 0.32530 0.75300 0.866 + O2a O 0.67470 0.16130 0.75300 0.866 + O2a O 0.83870 0.51340 0.75300 0.866 + O2a O 0.18007 0.00803 0.58033 0.866 + O2a O 0.99197 0.17203 0.58033 0.866 + O2a O 0.82797 0.81993 0.58033 0.866 + O2a O 0.15327 0.65863 0.08633 0.866 + O2a O 0.34137 0.49463 0.08633 0.866 + O2a O 0.50537 0.84673 0.08633 0.866 + O2a O 0.84673 0.34137 0.91367 0.866 + O2a O 0.65863 0.50537 0.91367 0.866 + O2a O 0.49463 0.15327 0.91367 0.866 + O2a O 0.81993 0.99197 0.41967 0.866 + O2a O 0.00803 0.82797 0.41967 0.866 + O2a O 0.17203 0.18007 0.41967 0.866 + O2b O 0.27560 0.80510 0.30870 0.866 + O2b O 0.19490 0.47050 0.30870 0.866 + O2b O 0.47050 0.27560 0.69130 0.866 + O2b O 0.72440 0.19490 0.69130 0.866 + O2b O 0.80510 0.52950 0.69130 0.866 + O2b O 0.19617 0.05773 0.64203 0.866 + O2b O 0.94227 0.13843 0.64203 0.866 + O2b O 0.86157 0.80383 0.64203 0.866 + O2b O 0.13717 0.60893 0.02463 0.866 + O2b O 0.39107 0.52823 0.02463 0.866 + O2b O 0.47177 0.86283 0.02463 0.866 + O2b O 0.86283 0.39107 0.97537 0.866 + O2b O 0.60893 0.47177 0.97537 0.866 + O2b O 0.52823 0.13717 0.97537 0.866 + O2b O 0.80383 0.94227 0.35797 0.866 + O2b O 0.05773 0.86157 0.35797 0.866 + O2b O 0.13843 0.19617 0.35797 0.866 + O3a O 0.86380 0.92280 0.96700 0.221 + O3a O 0.07720 0.94100 0.96700 0.221 + O3a O 0.94100 0.86380 0.03300 0.221 + O3a O 0.13620 0.07720 0.03300 0.221 + O3a O 0.92280 0.05900 0.03300 0.221 + O3a O 0.72567 0.46953 0.30033 0.221 + O3a O 0.53047 0.25613 0.30033 0.221 + O3a O 0.74387 0.27433 0.30033 0.221 + O3a O 0.60767 0.19713 0.36633 0.221 + O3a O 0.80287 0.41053 0.36633 0.221 + O3a O 0.58947 0.39233 0.36633 0.221 + O3a O 0.39233 0.80287 0.63367 0.221 + O3a O 0.19713 0.58947 0.63367 0.221 + O3a O 0.41053 0.60767 0.63367 0.221 + O3a O 0.27433 0.53047 0.69967 0.221 + O3a O 0.46953 0.74387 0.69967 0.221 + O3a O 0.25613 0.72567 0.69967 0.221 + O3b O 0.81700 0.89800 0.87900 0.221 + O3b O 0.10200 0.91900 0.87900 0.221 + O3b O 0.91900 0.81700 0.12100 0.221 + O3b O 0.18300 0.10200 0.12100 0.221 + O3b O 0.89800 0.08100 0.12100 0.221 + O3b O 0.74767 0.51633 0.21233 0.221 + O3b O 0.48367 0.23133 0.21233 0.221 + O3b O 0.76867 0.25233 0.21233 0.221 + O3b O 0.58567 0.15033 0.45433 0.221 + O3b O 0.84967 0.43533 0.45433 0.221 + O3b O 0.56467 0.41433 0.45433 0.221 + O3b O 0.41433 0.84967 0.54567 0.221 + O3b O 0.15033 0.56467 0.54567 0.221 + O3b O 0.43533 0.58567 0.54567 0.221 + O3b O 0.25233 0.48367 0.78767 0.221 + O3b O 0.51633 0.76867 0.78767 0.221 + O3b O 0.23133 0.74767 0.78767 0.221 diff --git a/test/data/crystals/symmetry_test_structure_cartn.cif b/test/data/crystals/symmetry_test_structure_cartn.cif new file mode 100644 index 000000000..555eabbee --- /dev/null +++ b/test/data/crystals/symmetry_test_structure_cartn.cif @@ -0,0 +1,53 @@ +# CIF file generated by openbabel 2.3.2, see http://openbabel.sf.net +data_I +_chemical_name_common '' +_cell_length_a 25.5177 +_cell_length_b 25.5177 +_cell_length_c 6.9661 +_cell_angle_alpha 90 +_cell_angle_beta 90 +_cell_angle_gamma 120 +_space_group_name_H-M_alt 'R -3' +_space_group_name_Hall '-R 3' +_symmetry_space_group_name_H-M 'R -3' +loop_ + _symmetry_equiv_pos_as_xyz + 'x,y,z' + '-y,x-y,z' + '-x+y,-x,z' + '-x,-y,-z' + 'y,-x+y,-z' + 'x-y,x,-z' + '2/3+x,1/3+y,1/3+z' + '2/3-y,1/3+x-y,1/3+z' + '2/3-x+y,1/3-x,1/3+z' + '2/3-x,1/3-y,1/3-z' + '2/3+y,1/3-x+y,1/3-z' + '2/3+x-y,1/3+x,1/3-z' + '1/3+x,2/3+y,2/3+z' + '1/3-y,2/3+x-y,2/3+z' + '1/3-x+y,2/3-x,2/3+z' + '1/3-x,2/3-y,2/3-z' + '1/3+y,2/3-x+y,2/3-z' + '1/3+x-y,2/3+x,2/3-z' +loop_ + _atom_site_type_symbol + _atom_site_label + _atom_site_Cartn_x + _atom_site_Cartn_y + _atom_site_Cartn_z + Fe Fe 5.42047 7.79254 1.02889 + O O1 4.35459 6.52141 2.43814 + O O2 4.87133 5.12254 4.10582 + O O3 5.52713 6.09490 0.00627 + C C1 4.96192 5.50043 2.88675 + C C2 5.71724 4.57670 1.93658 + C C3 5.95073 4.91481 0.58446 + C C4 6.65757 3.99108 -0.19296 + H H 6.80174 4.11262 -1.22255 + O O1a 5.71979 14.80410 4.71953 + O O1b 5.30130 13.95992 5.54153 + O O2a 4.49239 14.91018 1.72063 + O O2b 4.26911 16.00850 2.15044 + O O3a -0.23221 3.00988 6.73622 + O O3b -0.26794 4.04411 6.12320 From e0c965dd94e9856e2fdbea2023e6831b9a22ffc1 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 29 Aug 2019 14:36:53 -0700 Subject: [PATCH 059/105] able to read in bond info from cif files --- src/Crystal.jl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 36900e793..938979554 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -243,6 +243,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # iterate to next line in file i += 1 end + # set up graph of correct size + bonds = SimpleGraph(length(species_simple)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -279,6 +281,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # iterate to next line in file i += 1 end + # set up graph of correct size + bonds = SimpleGraph(length(species_simple)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -289,21 +293,16 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, elseif haskey(name_to_column, "_geom_bond_atom_site_label_1") && haskey(name_to_column, "_geom_bond_atom_site_label_2") && read_bonds_from_file - @printf("Inside Bond Reader\n") - # set up graph of correct size - bonds = SimpleGraph(length(species)) while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) atom_one_idx = label_num_to_idx[line[name_to_column["_geom_bond_atom_site_label_1"]]] atom_two_idx = label_num_to_idx[line[name_to_column["_geom_bond_atom_site_label_2"]]] - @printf("Connecting: %d %d\n", atom_one_idx, atom_two_idx) add_edge!(bonds, atom_one_idx, atom_two_idx) # iterate to next line in file i += 1 end - @printf("Total Number of bonds read: %d\nTotal Number of vertices: %d\n", ne(bonds), nv(bonds)) # skip to next iteration in outer while loop continue From 16e959bb4c0ed84c662ff06436b1dc121836a4fe Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 29 Aug 2019 14:46:44 -0700 Subject: [PATCH 060/105] fixed test file issue in simulation_rules_test --- test/simulation_rules_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/simulation_rules_test.jl b/test/simulation_rules_test.jl index bd27e69a5..6a908f68a 100644 --- a/test/simulation_rules_test.jl +++ b/test/simulation_rules_test.jl @@ -7,7 +7,7 @@ using Test # i.e. frameworks musts be in P1 symmetry to be used in GCMC or Henry # coefficient test @testset "Simulation Rules Tests" begin - non_P1_framework = Framework("ORIVOC_clean.cif", convert_to_p1=false) + non_P1_framework = Framework("symmetry_test_structure.cif", convert_to_p1=false) molecule = Molecule("CO2") ljff = LJForceField("UFF.csv") temp = 298.0 From 31b1ace8398bb8214530f96e40d2df6d2f827a84 Mon Sep 17 00:00:00 2001 From: Cory Simon Date: Fri, 30 Aug 2019 16:07:03 -0700 Subject: [PATCH 061/105] Update faq.md --- docs/src/guides/faq.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/guides/faq.md b/docs/src/guides/faq.md index 6d42295b9..01cdf6fae 100755 --- a/docs/src/guides/faq.md +++ b/docs/src/guides/faq.md @@ -16,8 +16,7 @@ Yes! See [here](https://github.com/JuliaLang/IJulia.jl). **How can I convert my `.cif` into P1 symmetry for `PorousMaterials.jl`?** `PorousMaterials.jl` will automatically do this for you! It looks for the -`_symmetry_equiv_pos_as_xyz` and use those rules to replicate the lower level -symmetry structure into P1 symmetry. +`_symmetry_equiv_pos_as_xyz` tag in the `.cif` file and uses those symmetry operations to replicate the structure in a lower symmetry into P1 symmetry. It is important to note that `PorousMaterials.jl` will read in the space group name, but it does **NOT** use this for converting your structure to P1. From 2d3072d0bfd5c9333abf2f13583f45ba235cd2a8 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 3 Sep 2019 11:44:22 -0700 Subject: [PATCH 062/105] added tests for checking the bond info from the write_cif function --- test/crystal_test.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index e16e6ba74..cc5ddcfe5 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -125,6 +125,10 @@ using Random BondingRule(:*, :*, 0.4, 1.9)] infer_bonds!(sbmof_bonds, bonding_rules) @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) + # write out and compare to inferred bonds + write_cif(sbmof_bonds, joinpath(pwd(), "data", "crystals", "SBMOF-1_inferred_bonds.cif")) + sbmof_read_bonds = Framework("SBMOF-1_inferred_bonds.cif") + @test compare_bonds_in_framework(sbmof_bonds, sbmof_read_bonds) # other bond info tests # TODO find more robust test/confirm these are the correct numbers @test ne(sbmof_bonds.bonds) == 128 From bb35d9a98aa279b8a29254e7093feb23f584f23a Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 3 Sep 2019 12:40:37 -0700 Subject: [PATCH 063/105] read_bonds_from_file defaults to false now. --- src/Crystal.jl | 2 +- test/crystal_test.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 938979554..16454b484 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -79,7 +79,7 @@ function it is assumed it is in P1 symmetry. function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, remove_overlap::Bool=false, convert_to_p1::Bool=true, - read_bonds_from_file::Bool=true) + read_bonds_from_file::Bool=false) # Read file extension. Ensure we can read the file type extension = split(filename, ".")[end] if ! (extension in ["cif", "cssr"]) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index cc5ddcfe5..00fdf5fd7 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -127,7 +127,7 @@ using Random @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) # write out and compare to inferred bonds write_cif(sbmof_bonds, joinpath(pwd(), "data", "crystals", "SBMOF-1_inferred_bonds.cif")) - sbmof_read_bonds = Framework("SBMOF-1_inferred_bonds.cif") + sbmof_read_bonds = Framework("SBMOF-1_inferred_bonds.cif"; read_bonds_from_file=true) @test compare_bonds_in_framework(sbmof_bonds, sbmof_read_bonds) # other bond info tests # TODO find more robust test/confirm these are the correct numbers From 387a180f919dd45f1837db044f1cbcc91c7832a4 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 3 Sep 2019 13:19:23 -0700 Subject: [PATCH 064/105] add testing file for reading in bonds --- test/crystal_test.jl | 11 +++++++-- test/data/crystals/test_bond_viz.cif | 36 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/data/crystals/test_bond_viz.cif diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 00fdf5fd7..1898793df 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -127,8 +127,8 @@ using Random @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) # write out and compare to inferred bonds write_cif(sbmof_bonds, joinpath(pwd(), "data", "crystals", "SBMOF-1_inferred_bonds.cif")) - sbmof_read_bonds = Framework("SBMOF-1_inferred_bonds.cif"; read_bonds_from_file=true) - @test compare_bonds_in_framework(sbmof_bonds, sbmof_read_bonds) + sbmof_inferred_bonds = Framework("SBMOF-1_inferred_bonds.cif"; read_bonds_from_file=true) + @test compare_bonds_in_framework(sbmof_bonds, sbmof_inferred_bonds) # other bond info tests # TODO find more robust test/confirm these are the correct numbers @test ne(sbmof_bonds.bonds) == 128 @@ -139,6 +139,13 @@ using Random @test ne(sbmof_bonds.bonds) == 0 @test !compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) + # test reading in bonds as part of `Framework()` + sbmof_read_bonds = Framework("test_bond_viz.cif"; read_bonds_from_file=true, check_atom_and_charge_overlap=false) + @test ne(sbmof_read_bonds.bonds) == 5 + write_cif(sbmof_read_bonds, joinpath(pwd(), "data", "crystals", "rewritten_sbmof_read_bonds.cif")) + reloaded_sbmof_read_bonds = Framework("rewritten_sbmof_read_bonds.cif"; read_bonds_from_file=true, check_atom_and_charge_overlap=false) + @test compare_bonds_in_framework(sbmof_read_bonds, reloaded_sbmof_read_bonds) + repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) @test replication_factors(replicated_sbmof.box, 14.0) == (1, 1, 1) diff --git a/test/data/crystals/test_bond_viz.cif b/test/data/crystals/test_bond_viz.cif new file mode 100644 index 000000000..e4ce798aa --- /dev/null +++ b/test/data/crystals/test_bond_viz.cif @@ -0,0 +1,36 @@ +data_MOF_publ +_cell_length_a 10.0 +_cell_length_b 10.0 +_cell_length_c 10.0 +_cell_angle_alpha 90.0 +_cell_angle_beta 90.0 +_cell_angle_gamma 90.0 + +_symmetry_space_group_name_H-M 'P1' + +loop_ +_symmetry_equiv_pos_as_xyz + 'x,y,z' + +loop_ +_atom_site_type_symbol +_atom_site_label +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z + C C1 0.1 0.1 0.1 + H H1 0.05 0.05 0.05 + O O1 0.5 0.5 0.5 + O O2 1.0 1.0 1.0 + O O3 1.0 1.0 0.0 + O O4 1.0 0.0 1.0 + O O5 0.0 1.0 1.0 + +loop_ +_geom_bond_atom_site_label_1 +_geom_bond_atom_site_label_2 + C1 O1 + C1 O2 + C1 O3 + C1 O4 + C1 O5 From 466948887a7119fbb53207f434c3cc971c0c48f0 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 4 Sep 2019 13:32:06 -0700 Subject: [PATCH 065/105] add an assertion to the energy_grid() and the compute_accessibility_grid() and add tests for them --- src/Grid.jl | 2 ++ test/simulation_rules_test.jl | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/Grid.jl b/src/Grid.jl index 1c1d64114..74a9e8f82 100644 --- a/src/Grid.jl +++ b/src/Grid.jl @@ -246,6 +246,7 @@ function energy_grid(framework::Framework, molecule::Molecule, ljforcefield::LJF n_pts::Tuple{Int, Int, Int}=(50,50,50), n_rotations::Int=1000, temperature::Float64=NaN, units::Symbol=:kJ_mol, center::Bool=false, verbose::Bool=true) + assert_P1_symmetry(framework) if ! (units in [:kJ_mol, :K]) error("Pass :kJ_mol or :K for units of kJ/mol or K, respectively.") end @@ -626,6 +627,7 @@ function compute_accessibility_grid(framework::Framework, probe::Molecule, force energy_tol::Float64=10.0, energy_units::Symbol=:kJ_mol, verbose::Bool=true, write_b4_after_grids::Bool=true, block_inaccessible_pockets::Bool=true) + assert_P1_symmetry(framework) if verbose printstyled(@sprintf("Computing accessibility grid of %s using %f %s potential energy tol and %s probe...\n", framework.name, energy_tol, energy_units, probe.species), color=:green) diff --git a/test/simulation_rules_test.jl b/test/simulation_rules_test.jl index 6a908f68a..c31be77b1 100644 --- a/test/simulation_rules_test.jl +++ b/test/simulation_rules_test.jl @@ -29,5 +29,11 @@ using Test # coefficient with a non-P1 framework @test_throws AssertionError henry_coefficient(non_P1_framework, molecule, temp, ljff) + + # Test that an assertion is thrown when a non-P1 structure is passed into + # energy_grid(), and compute_accessibility_grid() + @test_throws AssertionError energy_grid(non_P1_framework, molecule, ljff) + @test_throws AssertionError compute_accessibility_grid(non_P1_framework, molecule, ljff) + end end From d702346790be7eb141b86a8befad977d4584573e Mon Sep 17 00:00:00 2001 From: CorySimon Date: Thu, 5 Sep 2019 15:06:32 -0700 Subject: [PATCH 066/105] rectangular box constructor, fixed warnings in crystals for p1 --- src/Box.jl | 2 ++ src/Crystal.jl | 17 +++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Box.jl b/src/Box.jl index 9a622a0bc..18c0df81b 100644 --- a/src/Box.jl +++ b/src/Box.jl @@ -1,6 +1,7 @@ """ box = Box(a, b, c, α, β, γ, volume, f_to_c, c_to_f, reciprocal_lattice) box = Box(a, b, c, α, β, γ) + box = Box(a, b, c) # α=β=γ=π/2 assumed. box = Box(f_to_c) Data structure to describe a unit cell box (Bravais lattice) and convert between @@ -114,6 +115,7 @@ This function generates a unit cube, each side is 1.0 Angstrom long, and all the corners are right angles. """ UnitCube() = Box(1.0, 1.0, 1.0, π/2, π/2, π/2) +Box(a::Float64, b::Float64, c::Float64) = Box(a, b, c, π/2, π/2, π/2) # right angle box """ new_box = replicate(original_box, repfactors) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9ab4ae385..04d01a923 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -53,7 +53,7 @@ function it is assumed it is in P1 symmetry. - `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This is used before any simulations such as GCMC and Henry Coefficient - `wrap_to_unit_cell::Bool`: Whether the atom and charge positions will be - wrapped to the unit cell. + wrapped to the unit cell so their coordinates are in [0, 1] """ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, @@ -268,12 +268,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, error("If structure is not in P1 symmetry it must have replication information") end - # warning that structure is not being converted to P1 symmetry - if ! convert_to_p1 && ! p1_symmetry - @warn @sprintf("%s is not in P1 symmetry and it is not being converted to P1 symmetry.\nAny simulations performed with PorousMaterials will NOT be accurate", - filename) - end - a = data["a"] b = data["b"] c = data["c"] @@ -287,7 +281,10 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end if symmetry_info && convert_to_p1 - @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) + @warn @sprintf("%s is not in P1 symmetry. It is in %s symmetry. + We are converting it to P1 symmetry for use in PorousMaterials.jl. + To keep in the original lower-level symmetry, pass `convert_to_p1=false` to the `Framework` constructor.", filename, space_group) + # loop over all symmetry rules for i in 1:size(symmetry_rules, 2) new_col = Array{Float64, 1}(undef, 0) @@ -489,7 +486,7 @@ function atom_overlap(framework::Framework; overlap_tol::Float64=0.1, verbose::B framework.box, overlap_tol) overlap = true if verbose - @warn @sprintf("Atoms %d and %d in %s are less than %d Å apart.", i, j, + @warn @sprintf("Atoms %d and %d in %s are less than %f Å apart.", i, j, framework.name, overlap_tol) end end @@ -509,7 +506,7 @@ function charge_overlap(framework::Framework; overlap_tol::Float64=0.1, verbose: framework.box, overlap_tol) overlap = true if verbose - @warn @sprintf("Charges %d and %d in %s are less than %d Å apart.", i, j, + @warn @sprintf("Charges %d and %d in %s are less than %f Å apart.", i, j, framework.name, overlap_tol) end end From 7a1c4cf0c9d5cfe5793e4a3e26ee9d8d66bf5b27 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 9 Sep 2019 13:41:39 -0700 Subject: [PATCH 067/105] start pdb reader, adjust write cif to change formatting --- src/Crystal.jl | 76 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 16454b484..2148fd45d 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -380,6 +380,60 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # if structure was stored in P1 or converted to P1, store that information for later p1_symmetry = p1_symmetry || convert_to_p1 + # Start of pdb reader for bonding information check + elseif extension == "pdb" + # PDB's store atom locations in Å (Cartesian) + + for line in lines + # information in PDB's is tied to specific columns, hence the + # hard-coded ranges + line_type = line[1:6] + if line_type == "CRYST1" + elseif line_type == "HETATM" || line_type == "ATOM " + x = parse(Float64, line[31:38]) + y = parse(Float64, line[39:46]) + z = parse(Float64, line[47:54]) + # Add the new coordinates + coords = [coords [x, y, z]] + # Add the new atom species + push!(species, Symbol(strip(line[77:78]))) + # Add the charge (if there) + if length(strip(line[79:80])) == 0 + push!(charge_values, 0.0) + else + push!(charge_values, parse(Float64, strip(line[79:80]))) + end + # increase size of graph + add_vertex(bonds) + elseif line_type == "CONNECT" + connections = split(line)[2:end] + # Store the atom number + atom_num = parse(Float64, connections[1]) + connections = connections[2:end] + + # Loop through all connections this atom has and add them + for connection in connections + add_edge!(bonds, atom_num, parse(Float64, connection)) + end + # Use the Checksums! Yay Checksums! + elseif line_type == "MASTER" + number_atoms = parse(Int, line[51:55]) + number_connections = parse(Int, line[61:65]) + @assert number_atoms == size(coords, 2) @sprintf( + "Number of coordinates does not equal checksum\nNumber of coordinates: %d\tChecksum: %d\n", size(coords, 2), number_atoms) + @assert number_atoms == length(charges) @sprintf( + "Number of charges does not equal checksum\nNumber of charges: %d\tChecksum: %d\n", length(charges), number_atoms) + @assert number_atoms == length(species) @sprintf( + "Number of species does not equal checksum\nNumber of species: %d\tChecksum: %d\n", length(species), number_atoms) + @assert number_atoms == nv(bonds) @sprintf( + "Number of vertices in bond graph does not equal checksum\nNumber of vertices: %d\tChecksum: %d\n", nv(bonds), number_atoms) + @assert number_connections == ne(bonds) @sprintf( + "Number of edges in bond graph does not equal checksum\nNumber of edges: %d\tChecksum: %d\n", ne(bonds), number_connections) + end + end + + + # Start of cssr reader #TODO make sure this works for different .cssr files! elseif extension == "cssr" # First line contains unit cell lenghts @@ -1089,15 +1143,15 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B @printf(cif_file, "data_%s_PM\n", split(framework.name, ".")[1]) end - @printf(cif_file, "_symmetry_space_group_name_H-M \t'%s'\n", framework.space_group) + @printf(cif_file, "_symmetry_space_group_name_H-M\t'%s'\n", framework.space_group) - @printf(cif_file, "_cell_length_a \t%f\n", framework.box.a) - @printf(cif_file, "_cell_length_b \t%f\n", framework.box.b) - @printf(cif_file, "_cell_length_c \t%f\n", framework.box.c) + @printf(cif_file, "_cell_length_a\t%f\n", framework.box.a) + @printf(cif_file, "_cell_length_b\t%f\n", framework.box.b) + @printf(cif_file, "_cell_length_c\t%f\n", framework.box.c) - @printf(cif_file, "_cell_angle_alpha \t%f\n", framework.box.α * 180.0 / pi) - @printf(cif_file, "_cell_angle_beta \t%f\n", framework.box.β * 180.0 / pi) - @printf(cif_file, "_cell_angle_gamma \t%f\n", framework.box.γ * 180.0 / pi) + @printf(cif_file, "_cell_angle_alpha\t%f\n", framework.box.α * 180.0 / pi) + @printf(cif_file, "_cell_angle_beta\t%f\n", framework.box.β * 180.0 / pi) + @printf(cif_file, "_cell_angle_gamma\t%f\n", framework.box.γ * 180.0 / pi) @printf(cif_file, "_symmetry_Int_Tables_number 1\n\n") @printf(cif_file, "loop_\n_symmetry_equiv_pos_as_xyz\n") @@ -1124,7 +1178,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B end end # print label and type symbol - @printf(cif_file, "%s \t%s \t", string(framework.atoms.species[i]) * + @printf(cif_file, "%s\t%s\t", string(framework.atoms.species[i]) * string(label_numbers[framework.atoms.species[i]]), framework.atoms.species[i]) # store label for this atom idx @@ -1133,10 +1187,10 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B # increment label label_numbers[framework.atoms.species[i]] += 1 if fractional - @printf(cif_file, "%f \t%f \t%f \t%f\n", framework.atoms.xf[:, i]..., q) + @printf(cif_file, "%f\t%f\t%f\t%f\n", framework.atoms.xf[:, i]..., q) else - @printf(cif_file, "%f \t%f \t%f \t%f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) + @printf(cif_file, "%f\t%f\t%f\t%f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])..., q) end end @@ -1148,7 +1202,7 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B for edge in collect(edges(framework.bonds)) dxf = framework.atoms.xf[:, edge.src] - framework.atoms.xf[:, edge.dst] nearest_image!(dxf) - @printf(cif_file, "%s \t%s \t%0.5f\n", idx_to_label[edge.src], idx_to_label[edge.dst], + @printf(cif_file, "%s\t%s\t%0.5f\n", idx_to_label[edge.src], idx_to_label[edge.dst], norm(dxf)) end end From 4cade0b2b8267dff0f0014c29c70516f6c5b8233 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 9 Sep 2019 14:20:36 -0700 Subject: [PATCH 068/105] add CRYST1 tag interpreter to pdb reader, start working on cleaning up the apply symmetry rules --- src/Crystal.jl | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 2148fd45d..c122e6236 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -179,7 +179,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # ===================== # SYMMETRY READER # ===================== - if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") && ! p1_symmetry + if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") symmetry_info = true symmetry_count = 0 @@ -372,11 +372,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, species = deepcopy(species_simple) end - # either read in P1 or converted to P1 so should have same symmetry rules - if p1_symmetry || convert_to_p1 - symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] - end - # if structure was stored in P1 or converted to P1, store that information for later p1_symmetry = p1_symmetry || convert_to_p1 @@ -389,6 +384,19 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # hard-coded ranges line_type = line[1:6] if line_type == "CRYST1" + a = parse(Float64, line[7:15]) + b = parse(Float64, line[16:24]) + c = parse(Float64, line[25:33]) + α = parse(Float64, line[34:40]) + β = parse(Float64, line[41:47]) + γ = parse(Float64, line[48:54]) + space_group = strip(line[56:66]) + if space_group == "P1" || space_group == "P 1" || + space_group == "-P1" || space_group == "-P 1" + # simplify by only having one P1 space_group name + space_group = "P1" + p1_symmetry = true + end elseif line_type == "HETATM" || line_type == "ATOM " x = parse(Float64, line[31:38]) y = parse(Float64, line[39:46]) From 6bb275bb53fe1d33d0859cc09c76911df7f5831c Mon Sep 17 00:00:00 2001 From: Arthur York Date: Mon, 9 Sep 2019 17:02:36 -0700 Subject: [PATCH 069/105] remove artifacts of old converter within the cif reader. conversion now takes place after it has been read in and uses the apply_symmetry_rules function --- src/Crystal.jl | 63 +++++++++++++++++++------------------------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index c122e6236..49dd01907 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -114,9 +114,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Start of .cif reader if extension == "cif" - coords_simple = Array{Float64, 2}(undef, 3, 0) - charges_simple = Array{Float64, 1}() - species_simple = Array{Symbol, 1}() data = Dict{AbstractString, Float64}() loop_starts = -1 i = 1 @@ -226,25 +223,25 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + push!(species, Symbol(line[name_to_column[atom_column_name]])) + coords = [coords [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] # If charges present, import them if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) else - push!(charges_simple, 0.0) + push!(charge_values, 0.0) end # add to label_num_to_idx so that bonds can be converted later if read_bonds_from_file - label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species) end # iterate to next line in file i += 1 end # set up graph of correct size - bonds = SimpleGraph(length(species_simple)) + bonds = SimpleGraph(length(species)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -264,25 +261,25 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), + push!(species, Symbol(line[name_to_column[atom_column_name]])) + coords = [coords [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] # If charges present, import them if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) else - push!(charges_simple, 0.0) + push!(charge_values, 0.0) end # add to label_num_to_idx so that bonds can be converted later if read_bonds_from_file - label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species) end # iterate to next line in file i += 1 end # set up graph of correct size - bonds = SimpleGraph(length(species_simple)) + bonds = SimpleGraph(length(species)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -350,31 +347,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # redo coordinates if they were read in cartesian if cartesian && ! fractional - coords_simple = Box(a, b, c, α, β, γ).c_to_f * coords_simple + coords = Box(a, b, c, α, β, γ).c_to_f * coords end - if symmetry_info && convert_to_p1 - @assert ne(bonds) == 0 @sprintf("Cannot apply symmetry rules to a structure with bonds present. Do not use the argument `read_bonds_from_file` and insterad use `infer_bonds`once the structure is read in.") - @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) - # loop over all symmetry rules - for i in 1:size(symmetry_rules, 2) - new_col = Array{Float64, 1}(undef, 0) - # loop over all atom positions from lower level symmetry - for j in 1:size(coords_simple, 2) - coords = [coords [Base.invokelatest(eval(Meta.parse("(x, y, z) -> " * symmetry_rules[k, i])), coords_simple[:, j]...) for k in 1:3]] - end - charge_values = [charge_values; charges_simple] - species = [species; species_simple] - end - elseif p1_symmetry || !convert_to_p1 - coords = deepcopy(coords_simple) - charge_values = deepcopy(charges_simple) - species = deepcopy(species_simple) - end - - # if structure was stored in P1 or converted to P1, store that information for later - p1_symmetry = p1_symmetry || convert_to_p1 - # Start of pdb reader for bonding information check elseif extension == "pdb" # PDB's store atom locations in Å (Cartesian) @@ -501,6 +476,12 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, strip_numbers_from_atom_labels!(framework) + if convert_to_p1 && ! read_bonds_from_file + return apply_symmetry_rules(framework; remove_overlap=remove_overlap, + check_charge_neutrality=check_charge_neutrality, + check_atom_and_charge_overlap=check_atom_and_charge_overlap) + end + if remove_overlap return remove_overlapping_atoms_and_charges(framework) end @@ -894,7 +875,11 @@ function crystal_density(framework::Framework) end """ - simulation_ready_framework = apply_symmetry_rules(non_p1_framework) + simulation_ready_framework = apply_symmetry_rules(non_p1_framework; + check_charge_neutrality=true, + net_charge_tol=0.001, + check_atom_and_charge_overlap=true, + remove_overlap=false) Convert a framework to P1 symmetry based on internal symmetry rules. This will return the new framework. From 3f9262e42837e15c7fe7091f4bf6630dd01a112a Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 10 Sep 2019 15:59:12 -0700 Subject: [PATCH 070/105] moved symmetry application to outside the cif reader, make it easier to add new file type later --- src/Crystal.jl | 70 ++++++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 45 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 16454b484..b226b3fb0 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -114,9 +114,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Start of .cif reader if extension == "cif" - coords_simple = Array{Float64, 2}(undef, 3, 0) - charges_simple = Array{Float64, 1}() - species_simple = Array{Symbol, 1}() data = Dict{AbstractString, Float64}() loop_starts = -1 i = 1 @@ -179,7 +176,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # ===================== # SYMMETRY READER # ===================== - if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") && ! p1_symmetry + if haskey(name_to_column, "_symmetry_equiv_pos_as_xyz") symmetry_info = true symmetry_count = 0 @@ -226,25 +223,25 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - coords_simple = [coords_simple [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), + push!(species, Symbol(line[name_to_column[atom_column_name]])) + coords = [coords [mod(parse(Float64, split(line[name_to_column["_atom_site_fract_x"]], '(')[1]), 1.0), mod(parse(Float64, split(line[name_to_column["_atom_site_fract_y"]], '(')[1]), 1.0), mod(parse(Float64, split(line[name_to_column["_atom_site_fract_z"]], '(')[1]), 1.0)]] # If charges present, import them if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) else - push!(charges_simple, 0.0) + push!(charge_values, 0.0) end # add to label_num_to_idx so that bonds can be converted later if read_bonds_from_file - label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species) end # iterate to next line in file i += 1 end # set up graph of correct size - bonds = SimpleGraph(length(species_simple)) + bonds = SimpleGraph(length(species)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -264,25 +261,25 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) - push!(species_simple, Symbol(line[name_to_column[atom_column_name]])) - coords_simple = [coords_simple [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), + push!(species, Symbol(line[name_to_column[atom_column_name]])) + coords = [coords [parse(Float64, split(line[name_to_column["_atom_site_Cartn_x"]], '(')[1]), parse(Float64, split(line[name_to_column["_atom_site_Cartn_y"]], '(')[1]), parse(Float64, split(line[name_to_column["_atom_site_Cartn_z"]], '(')[1])]] # If charges present, import them if haskey(name_to_column, "_atom_site_charge") - push!(charges_simple, parse(Float64, line[name_to_column["_atom_site_charge"]])) + push!(charge_values, parse(Float64, line[name_to_column["_atom_site_charge"]])) else - push!(charges_simple, 0.0) + push!(charge_values, 0.0) end # add to label_num_to_idx so that bonds can be converted later if read_bonds_from_file - label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species_simple) + label_num_to_idx[line[name_to_column["_atom_site_label"]]] = length(species) end # iterate to next line in file i += 1 end # set up graph of correct size - bonds = SimpleGraph(length(species_simple)) + bonds = SimpleGraph(length(species)) # finish reading in atom_site information, skip to next # iteration of outer while-loop # prevents skipping a line after finishing reading atoms @@ -350,36 +347,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # redo coordinates if they were read in cartesian if cartesian && ! fractional - coords_simple = Box(a, b, c, α, β, γ).c_to_f * coords_simple + coords = Box(a, b, c, α, β, γ).c_to_f * coords end - if symmetry_info && convert_to_p1 - @assert ne(bonds) == 0 @sprintf("Cannot apply symmetry rules to a structure with bonds present. Do not use the argument `read_bonds_from_file` and insterad use `infer_bonds`once the structure is read in.") - @warn @sprintf("%s is not in P1 symmetry. It is being converted to P1 for use in PorousMaterials.jl.", filename) - # loop over all symmetry rules - for i in 1:size(symmetry_rules, 2) - new_col = Array{Float64, 1}(undef, 0) - # loop over all atom positions from lower level symmetry - for j in 1:size(coords_simple, 2) - coords = [coords [Base.invokelatest(eval(Meta.parse("(x, y, z) -> " * symmetry_rules[k, i])), coords_simple[:, j]...) for k in 1:3]] - end - charge_values = [charge_values; charges_simple] - species = [species; species_simple] - end - elseif p1_symmetry || !convert_to_p1 - coords = deepcopy(coords_simple) - charge_values = deepcopy(charges_simple) - species = deepcopy(species_simple) - end - - # either read in P1 or converted to P1 so should have same symmetry rules - if p1_symmetry || convert_to_p1 - symmetry_rules = [Array{AbstractString, 2}(undef, 3, 0) ["x", "y", "z"]] - end - - # if structure was stored in P1 or converted to P1, store that information for later - p1_symmetry = p1_symmetry || convert_to_p1 - # Start of cssr reader #TODO make sure this works for different .cssr files! elseif extension == "cssr" # First line contains unit cell lenghts @@ -439,6 +409,12 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, strip_numbers_from_atom_labels!(framework) + if convert_to_p1 && ! read_bonds_from_file + return apply_symmetry_rules(framework; remove_overlap=remove_overlap, + check_charge_neutrality=check_charge_neutrality, + check_atom_and_charge_overlap=check_atom_and_charge_overlap) + end + if remove_overlap return remove_overlapping_atoms_and_charges(framework) end @@ -832,7 +808,11 @@ function crystal_density(framework::Framework) end """ - simulation_ready_framework = apply_symmetry_rules(non_p1_framework) + simulation_ready_framework = apply_symmetry_rules(non_p1_framework; + check_charge_neutrality=true, + net_charge_tol=0.001, + check_atom_and_charge_overlap=true, + remove_overlap=false) Convert a framework to P1 symmetry based on internal symmetry rules. This will return the new framework. From 5b19669728ccf6846117baf9833df31aa9103fc4 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 10 Sep 2019 18:12:26 -0700 Subject: [PATCH 071/105] pre-allocate space for symmetry rules --- src/Crystal.jl | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 49dd01907..07628e8bc 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -476,7 +476,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, strip_numbers_from_atom_labels!(framework) - if convert_to_p1 && ! read_bonds_from_file + if convert_to_p1 && ! p1_symmetry && ! read_bonds_from_file return apply_symmetry_rules(framework; remove_overlap=remove_overlap, check_charge_neutrality=check_charge_neutrality, check_atom_and_charge_overlap=check_atom_and_charge_overlap) @@ -898,8 +898,11 @@ return the new framework. function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, remove_overlap::Bool=false) - new_atom_xfs = Array{Float64, 2}(undef, 3, 0) - new_charge_xfs = Array{Float64, 2}(undef, 3, 0) + if framework.is_p1 + return framework + end + new_atom_xfs = Array{Float64, 2}(undef, 3, framework.atoms.n_atoms * size(framework.symmetry, 2)) + new_charge_xfs = Array{Float64, 2}(undef, 3, framework.charges.n_charges * size(framework.symmetry, 2)) new_atom_species = Array{Symbol, 1}(undef, 0) new_charge_qs = Array{Float64, 1}(undef, 0) @@ -908,16 +911,18 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo # loop over all atoms in lower level symmetry for j in 1:size(framework.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_atom_xfs = [new_atom_xfs [Base.invokelatest.( + current_atom_idx = (i - 1) * framework.atoms.n_atoms + j + new_atom_xfs[:, current_atom_idx] .= [Base.invokelatest.( eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), - framework.atoms.xf[:, j]...) for k in 1:3]] + framework.atoms.xf[:, j]...) for k in 1:3] end # loop over all charges in lower level symmetry for j in 1:size(framework.charges.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates - new_charge_xfs = [new_charge_xfs [Base.invokelatest.( + current_charge_idx = (i - 1) * framework.charges.n_charges + j + new_charge_xfs[:, current_charge_idx] .= [Base.invokelatest.( eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), - framework.charges.xf[:, j]...) for k in 1:3]] + framework.charges.xf[:, j]...) for k in 1:3] end # repeat charge_qs and atom_species for every symmetry applied new_atom_species = [new_atom_species; framework.atoms.species] From d4caabace735da4c4d89eb96b15f9ef9c19dbb9e Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 11 Sep 2019 12:56:03 -0700 Subject: [PATCH 072/105] fix bond comparison function, works correctly --- src/Crystal.jl | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index b19ce8704..878d7be20 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1025,21 +1025,17 @@ function compare_bonds_in_framework(f1::Framework, f2::Framework) return false end - other_bonds = deepcopy(f2.bonds) - + num_in_common = 0 for edge_i in collect(edges(f1.bonds)) - set_i = Set([(f1.atoms.xf[:, edge_i.src], f1.atoms.species[edge_i.src]), - (f1.atoms.xf[:, edge_i.dst], f1.atoms.species[edge_i.dst])]) - for edge_j in collect(edges(other_bonds)) - set_j = Set([(f2.atoms.xf[:, edge_j.src], f2.atoms.species[edge_j.src]), - (f2.atoms.xf[:, edge_j.dst], f2.atoms.species[edge_j.dst])]) - if issetequal(set_i, set_j) - rem_edge!(other_bonds, edge_j.src, edge_j.dst) + for edge_j in collect(edges(f2.bonds)) + if (edge_i.src == edge_j.src && edge_i.dst == edge_j.dst) || + (edge_i.src == edge_j.dst && edge_i.dst == edge_j.src) + num_in_common += 1 break end end end - return ne(other_bonds) == 0 + return num_in_common == ne(f1.bonds) && num_in_common == ne(f2.bonds) end """ From f2df73edfb0100e6fc3300c97b509bfd48b8b0a6 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 11 Sep 2019 14:22:08 -0700 Subject: [PATCH 073/105] add more headers in framework constructor, add test comparing pdb bonds to the inferred bonds --- src/Crystal.jl | 6 ++++++ test/crystal_test.jl | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 878d7be20..23f779d5c 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -113,6 +113,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Start of .cif reader + ################################### + # CIF READER + ################################### if extension == "cif" data = Dict{AbstractString, Float64}() loop_starts = -1 @@ -351,6 +354,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end # Start of cssr reader #TODO make sure this works for different .cssr files! + ################################### + # CSSR READER + ################################### elseif extension == "cssr" # First line contains unit cell lenghts line = split(lines[1]) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 1898793df..ed19c5cbe 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -121,7 +121,7 @@ using Random # test replication no bonds assertion sbmof_bonds = Framework("SBMOF-1.cif") bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), - BondingRule(:Ca, :O, 0.4, 2.3), + BondingRule(:Ca, :O, 0.4, 2.5), BondingRule(:*, :*, 0.4, 1.9)] infer_bonds!(sbmof_bonds, bonding_rules) @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) @@ -131,7 +131,9 @@ using Random @test compare_bonds_in_framework(sbmof_bonds, sbmof_inferred_bonds) # other bond info tests # TODO find more robust test/confirm these are the correct numbers - @test ne(sbmof_bonds.bonds) == 128 + # replacing this test with the one below comparing pdb bond info to inferred + # bond info + #@test ne(sbmof_bonds.bonds) == 128 sbmof_bonds_copy = Framework("SBMOF-1.cif") infer_bonds!(sbmof_bonds_copy, bonding_rules) @test compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) @@ -146,6 +148,16 @@ using Random reloaded_sbmof_read_bonds = Framework("rewritten_sbmof_read_bonds.cif"; read_bonds_from_file=true, check_atom_and_charge_overlap=false) @test compare_bonds_in_framework(sbmof_read_bonds, reloaded_sbmof_read_bonds) + # Test that reading in bonding information is the same as inferring the + # bonding info + # Bonding information is from a pdb file saved by avogadro + # using non-p1 because it meant copying over fewer bonds + read_bonds = Framework("KAXQIL_clean_cartn.cif"; convert_to_p1=false, read_bonds_from_file=true) + inferred_bonds = Framework("KAXQIL_clean.cif"; convert_to_p1=false) + # Using same bonding rules as above + infer_bonds!(inferred_bonds, bonding_rules) + @test compare_bonds_in_framework(read_bonds, inferred_bonds) + repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) @test replication_factors(replicated_sbmof.box, 14.0) == (1, 1, 1) From af260c3e00243ce6e5549bdea866d0fc11888e85 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 11 Sep 2019 15:57:27 -0700 Subject: [PATCH 074/105] add testing files for new crystal tests --- test/data/crystals/KAXQIL_clean.cif | 72 ++++++++++++++++++ test/data/crystals/KAXQIL_clean_cartn.cif | 89 +++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 test/data/crystals/KAXQIL_clean.cif create mode 100644 test/data/crystals/KAXQIL_clean_cartn.cif diff --git a/test/data/crystals/KAXQIL_clean.cif b/test/data/crystals/KAXQIL_clean.cif new file mode 100644 index 000000000..e539e59fa --- /dev/null +++ b/test/data/crystals/KAXQIL_clean.cif @@ -0,0 +1,72 @@ + +####################################################################### +# +# Cambridge Crystallographic Data Centre +# CCDC +# +####################################################################### +# +# If this CIF has been generated from an entry in the Cambridge +# Structural Database, then it will include bibliographic, chemical, +# crystal, experimental, refinement or atomic coordinate data resulting +# from the CCDC's data processing and validation procedures. +# +####################################################################### + +data_casdb +_symmetry_cell_setting monoclinic +_symmetry_space_group_name_H-M 'P 21/n' +_symmetry_Int_Tables_number 14 +_space_group_name_Hall '-P 2yn' +loop_ +_symmetry_equiv_pos_site_id +_symmetry_equiv_pos_as_xyz +1 x,y,z +2 1/2-x,1/2+y,1/2-z +3 -x,-y,-z +4 1/2+x,1/2-y,1/2+z +_cell_length_a 11.8214(3) +_cell_length_b 5.56730(13) +_cell_length_c 22.7603(5) +_cell_angle_alpha 90.00 +_cell_angle_beta 100.356(2) +_cell_angle_gamma 90.00 +_cell_volume 1473.53 +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +Ca1 Ca 0.36318(3) 0.24415(5) 0.277126(14) +S1 S 0.60165(3) 0.03637(7) 0.392320(16) +O6 O 0.50690(10) 0.1950(2) 0.36904(5) +O1 O 0.73774(11) 0.0848(2) 0.69064(5) +O5 O 0.58466(12) -0.2189(2) 0.38824(6) +O3 O 1.01577(12) 0.4743(2) 0.25622(6) +O4 O 1.04305(12) 0.0809(2) 0.24715(7) +C4 C 0.99546(14) 0.2586(3) 0.26511(8) +C4A C 0.72326(14) 0.2607(3) 0.65507(7) +C6 C 0.64494(14) 0.1092(3) 0.46864(7) +C7 C 0.71894(14) 0.1104(3) 0.35781(7) +C8 C 0.80704(16) -0.0567(3) 0.36004(8) +H8 H 0.8055 -0.1996 0.3811 +C9 C 0.72975(17) -0.0114(3) 0.56772(8) +H9 H 0.7682 -0.1249 0.5939 +C10 C 0.69872(14) 0.2073(3) 0.58931(7) +C11 C 0.61656(16) 0.3313(3) 0.48920(8) +H11 H 0.5794 0.4459 0.4628 +C12 C 0.72051(15) 0.3272(3) 0.32810(8) +H12 H 0.6612 0.4377 0.3271 +C13 C 0.81276(15) 0.3756(3) 0.29975(8) +H13 H 0.8161 0.5217 0.2803 +C14 C 0.89738(16) -0.0071(3) 0.33040(9) +H14 H 0.9561 -0.1186 0.3309 +C15 C 0.70345(18) -0.0610(3) 0.50695(8) +H15 H 0.7249 -0.2066 0.4922 +C16 C 0.90018(14) 0.2083(3) 0.30006(7) +C17 C 0.64454(16) 0.3796(3) 0.54979(8) +H17 H 0.6269 0.5287 0.5642 +O2 O 0.73242(11) 0.4749(2) 0.67168(6) + +#END diff --git a/test/data/crystals/KAXQIL_clean_cartn.cif b/test/data/crystals/KAXQIL_clean_cartn.cif new file mode 100644 index 000000000..b05f05bc4 --- /dev/null +++ b/test/data/crystals/KAXQIL_clean_cartn.cif @@ -0,0 +1,89 @@ +data_KAXQIL_clean_PM +_symmetry_space_group_name_H-M 'P 21/n' +_cell_length_a 11.821400 +_cell_length_b 5.567300 +_cell_length_c 22.760300 +_cell_angle_alpha 90.000000 +_cell_angle_beta 100.356000 +_cell_angle_gamma 90.000000 +_symmetry_Int_Tables_number 1 + +loop_ +_symmetry_equiv_pos_as_xyz +'x,y,z' +'1/2-x,1/2+y,1/2-z' +'-x,-y,-z' +'1/2+x,1/2-y,1/2+z' + +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_Cartn_x +_atom_site_Cartn_y +_atom_site_Cartn_z +_atom_site_charge +Ca1 Ca 3.159441 1.359256 6.204721 0.000000 +S1 S 5.507177 0.202483 8.783861 0.000000 +O1 O 4.482349 1.085624 8.262632 0.000000 +O2 O 5.895382 0.472107 15.463105 0.000000 +O3 O 5.323025 4.348618 8.692511 0.000000 +O4 O -0.861895 2.640570 5.736645 0.000000 +O5 O -0.502297 0.450395 5.533572 0.000000 +C1 C 10.683039 1.439704 5.935688 0.000000 +C2 C 5.869742 1.451395 14.666710 0.000000 +C3 C 5.706664 0.607949 10.492630 0.000000 +C4 C 7.034906 0.614630 8.011198 0.000000 +C5 C 8.067247 5.251634 8.061127 0.000000 +H1 H 7.962876 4.456067 8.532650 0.000000 +C6 C 6.303853 5.503833 12.710984 0.000000 +H2 H 6.651271 4.871944 13.297142 0.000000 +C7 C 5.848700 1.154101 13.194374 0.000000 +C8 C 5.287052 1.844446 10.952958 0.000000 +H3 H 4.955784 2.482459 10.361875 0.000000 +C9 C 7.175023 1.821621 7.346005 0.000000 +H4 H 6.477987 2.436807 7.323615 0.000000 +C10 C 8.381541 2.091078 6.711262 0.000000 +H5 H 8.500603 2.904460 6.275785 0.000000 +C11 C 9.256464 5.527772 7.397501 0.000000 +H6 H 9.948571 4.907018 7.408696 0.000000 +C12 C 6.241589 5.227695 11.350372 0.000000 +H7 H 6.555508 4.417096 11.020127 0.000000 +C13 C 9.413699 1.159669 6.718202 0.000000 +C14 C 5.369912 2.113347 12.309540 0.000000 +H8 H 5.102424 2.943432 12.632173 0.000000 +O6 O 5.910066 2.643911 15.038600 0.000000 + +loop_ +_geom_bond_atom_site_label_1 +_geom_bond_atom_site_label_2 +Ca1 O1 +S1 C4 +S1 O1 +S1 O3 +S1 C3 +O2 C2 +O4 C1 +O5 C1 +C1 C13 +C2 C7 +C2 O6 +C3 C8 +C3 C12 +C4 C9 +C4 C5 +C5 C11 +C5 H1 +C6 C12 +C6 C7 +C6 H2 +C7 C14 +C8 H3 +C8 C14 +C9 C10 +C9 H4 +C10 H5 +C10 C13 +C11 C13 +C11 H6 +C12 H7 +C14 H8 From c15a4305c4d851988a5b059d6c4640c094cdb188 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 12 Sep 2019 12:45:35 -0700 Subject: [PATCH 075/105] fix bond comparison --- src/Crystal.jl | 34 +++++++++++++++++++++++----------- test/crystal_test.jl | 6 ++++-- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 23f779d5c..d90b2ec46 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -124,6 +124,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, symmetry_info = false atom_info = false label_num_to_idx = Dict{AbstractString, Int}() + fractional = false + cartesian = false while i <= length(lines) line = split(lines[i]) # Skip empty lines @@ -165,11 +167,11 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, i += 1 end - fractional = haskey(name_to_column, "_atom_site_fract_x") && + fractional = fractional || haskey(name_to_column, "_atom_site_fract_x") && haskey(name_to_column, "_atom_site_fract_y") && haskey(name_to_column, "_atom_site_fract_z") # if the file provides cartesian coordinates - cartesian = haskey(name_to_column, "_atom_site_Cartn_x") && + cartesian = cartesian || haskey(name_to_column, "_atom_site_Cartn_x") && haskey(name_to_column, "_atom_site_Cartn_y") && haskey(name_to_column, "_atom_site_Cartn_z") && ! fractional # if both are provided, will default @@ -983,7 +985,7 @@ pair doesn't have a suitable rule then they will not be considered bonded. good idea to include a bonding rule between two `:*` to allow any atoms to bond as long as they are close enough. """ -function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}) +function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}; include_bonds_across_periodic_boundaries::Bool=true) @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them with the `remove_bonds!` function before inferring new ones.", framework.name) # loop over every atom @@ -1009,7 +1011,9 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} end # determine if they are within range dxf = framework.atoms.xf[:, i] - framework.atoms.xf[:, j] - nearest_image!(dxf) + if include_bonds_across_periodic_boundaries + nearest_image!(dxf) + end norm_c = norm(framework.box.f_to_c * dxf) if species_match && br.min_dist < norm_c && norm_c < br.max_dist add_edge!(framework.bonds, i, j) @@ -1026,22 +1030,30 @@ end Returns whether the bonds defined in framework1 are the same as the bonds defined in framework2. """ -function compare_bonds_in_framework(f1::Framework, f2::Framework) - if ne(f1.bonds) != ne(f2.bonds) +function compare_bonds_in_framework(fi::Framework, fj::Framework; atol::Float64=0.0) + if ne(fi.bonds) != ne(fj.bonds) return false end num_in_common = 0 - for edge_i in collect(edges(f1.bonds)) - for edge_j in collect(edges(f2.bonds)) - if (edge_i.src == edge_j.src && edge_i.dst == edge_j.dst) || - (edge_i.src == edge_j.dst && edge_i.dst == edge_j.src) + for edge_i in collect(edges(fi.bonds)) + for edge_j in collect(edges(fj.bonds)) + # either the bond matches going src-src dst-dst + if (fi.atoms.species[edge_i.src] == fj.atoms.species[edge_j.src] && + fi.atoms.species[edge_i.dst] == fj.atoms.species[edge_j.dst] && + isapprox(fi.atoms.xf[:, edge_i.src], fj.atoms.xf[:, edge_j.src]; atol=atol) && + isapprox(fi.atoms.xf[:, edge_i.dst], fj.atoms.xf[:, edge_j.dst]; atol=atol)) || + # or the bond matches going src-dst dst-src + (fi.atoms.species[edge_i.src] == fj.atoms.species[edge_j.dst] && + fi.atoms.species[edge_i.dst] == fj.atoms.species[edge_j.src] && + isapprox(fi.atoms.xf[:, edge_i.src], fj.atoms.xf[:, edge_j.dst]; atol=atol) && + isapprox(fi.atoms.xf[:, edge_i.dst], fj.atoms.xf[:, edge_j.src]; atol=atol)) num_in_common += 1 break end end end - return num_in_common == ne(f1.bonds) && num_in_common == ne(f2.bonds) + return num_in_common == ne(fi.bonds) && num_in_common == ne(fj.bonds) end """ diff --git a/test/crystal_test.jl b/test/crystal_test.jl index ed19c5cbe..24b15d5e6 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -133,8 +133,10 @@ using Random # TODO find more robust test/confirm these are the correct numbers # replacing this test with the one below comparing pdb bond info to inferred # bond info - #@test ne(sbmof_bonds.bonds) == 128 sbmof_bonds_copy = Framework("SBMOF-1.cif") + # reverse the order of the atoms and bond info should still be the same + sbmof_bonds_copy.atoms.xf .= reverse(sbmof_bonds_copy.atoms.xf; dims=2) + sbmof_bonds_copy.atoms.species .= reverse(sbmof_bonds_copy.atoms.species) infer_bonds!(sbmof_bonds_copy, bonding_rules) @test compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) remove_bonds!(sbmof_bonds) @@ -156,7 +158,7 @@ using Random inferred_bonds = Framework("KAXQIL_clean.cif"; convert_to_p1=false) # Using same bonding rules as above infer_bonds!(inferred_bonds, bonding_rules) - @test compare_bonds_in_framework(read_bonds, inferred_bonds) + @test compare_bonds_in_framework(read_bonds, inferred_bonds; atol=1e-6) repfactors = replication_factors(sbmof.box, 14.0) replicated_sbmof = replicate(sbmof, repfactors) From 0140679e137e6367cdcdd9bcbaf2e6d7ec2ee3ac Mon Sep 17 00:00:00 2001 From: Arthur York Date: Thu, 12 Sep 2019 16:22:39 -0700 Subject: [PATCH 076/105] add bond writer, fix infer_bonds for speed and TODOs --- src/Crystal.jl | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index d90b2ec46..9cc7d2d89 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -462,6 +462,7 @@ construct a new `Framework`. Note `replicate(framework, (1, 1, 1))` returns the - `replicated_frame::Framework`: Replicated framework """ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) + # TODO name the remove_bonds func @assert ne(framework.bonds) == 0 @sprintf("The framework %s has bonds within it. Remove the bonds to replicate, and then use `infer_bonds(framework)` to recalculate bond information", framework.name) # determine number of atoms in replicated framework n_atoms = size(framework.atoms.xf, 2) * repfactors[1] * repfactors[2] * repfactors[3] @@ -501,6 +502,7 @@ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) space_group=framework.space_group, is_p1=framework.is_p1) end + # doc string in Misc.jl function write_xyz(framework::Framework, filename::AbstractString; comment::AbstractString="", center::Bool=false) @@ -985,6 +987,7 @@ pair doesn't have a suitable rule then they will not be considered bonded. good idea to include a bonding rule between two `:*` to allow any atoms to bond as long as they are close enough. """ +# TODO make default bonding rules with H* and ** function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}; include_bonds_across_periodic_boundaries::Bool=true) @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them with the `remove_bonds!` function before inferring new ones.", framework.name) @@ -1009,15 +1012,17 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} (framework.atoms.species[j] == br.species_i && framework.atoms.species[i] == br.species_j) species_match = true end - # determine if they are within range - dxf = framework.atoms.xf[:, i] - framework.atoms.xf[:, j] - if include_bonds_across_periodic_boundaries - nearest_image!(dxf) - end - norm_c = norm(framework.box.f_to_c * dxf) - if species_match && br.min_dist < norm_c && norm_c < br.max_dist - add_edge!(framework.bonds, i, j) - break + if species_match + # determine if they are within range + dxf = framework.atoms.xf[:, i] - framework.atoms.xf[:, j] + if include_bonds_across_periodic_boundaries + nearest_image!(dxf) + end + norm_c = norm(framework.box.f_to_c * dxf) + if br.min_dist < norm_c && norm_c < br.max_dist + add_edge!(framework.bonds, i, j) + break + end end end end @@ -1154,6 +1159,27 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B close(cif_file) end +""" +""" +function write_bond_information(framework::Framework, filename::AbstractString) + if ! occursin(".vtk", filename) + filename *= ".vtk" + end + + vtk_file = open(filename, "w") + + @printf(vtk_file, "# vtk DataFile Version 2.0\n%s bond information\nASCII\nDATASET POLYDATA\nPOINTS %d double\n", framework.name, nv(framework.bonds)) + + for i = 1:framework.atoms.n_atoms + @printf(vtk_file, "%0.5f\t%0.5f\t%0.5f\n", (framework.box.f_to_c * framework.atoms.xf[:, i])...) + end + @printf(vtk_file, "\nLINES %d %d\n", ne(framework.bonds), 3 * ne(framework.bonds)) + for edge in collect(edges(framework.bonds)) + @printf(vtk_file, "2\t%d\t%d\n", edge.src - 1, edge.dst - 1) + end + close(vtk_file) +end + """ new_framework = assign_charges(framework, charges, net_charge_tol=1e-5) From 28699c6e80bf45a570a4f3d84b95fb43b899573f Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 13 Sep 2019 11:28:16 -0700 Subject: [PATCH 077/105] generate symmetry rules once when they are used as a group instead of once for every atom --- src/Crystal.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9cc7d2d89..6f8371efb 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -852,20 +852,19 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo # for each symmetry rule for i in 1:size(framework.symmetry, 2) # loop over all atoms in lower level symmetry + sym_rule = eval.(Meta.parse.("(x, y, z) -> " .* framework.symmetry[:, i])) for j in 1:size(framework.atoms.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates current_atom_idx = (i - 1) * framework.atoms.n_atoms + j new_atom_xfs[:, current_atom_idx] .= [Base.invokelatest.( - eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), - framework.atoms.xf[:, j]...) for k in 1:3] + sym_rule[k], framework.atoms.xf[:, j]...) for k in 1:3] end # loop over all charges in lower level symmetry for j in 1:size(framework.charges.xf, 2) # apply current symmetry rule to current atom for x, y, and z coordinates current_charge_idx = (i - 1) * framework.charges.n_charges + j new_charge_xfs[:, current_charge_idx] .= [Base.invokelatest.( - eval(Meta.parse("(x, y, z) -> " * framework.symmetry[k, i])), - framework.charges.xf[:, j]...) for k in 1:3] + sym_rule[k], framework.charges.xf[:, j]...) for k in 1:3] end # repeat charge_qs and atom_species for every symmetry applied new_atom_species = [new_atom_species; framework.atoms.species] From 7a89f702c6399a0818e87f6885b5de3c875d3d98 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 13 Sep 2019 13:11:20 -0700 Subject: [PATCH 078/105] add documentation for function headers, add demo of using bond information to the docs site. --- docs/src/manual/boxes_crystals_grids.md | 66 +++++++++++++++++++++++++ src/Crystal.jl | 29 ++++++++++- src/PorousMaterials.jl | 1 + 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/docs/src/manual/boxes_crystals_grids.md b/docs/src/manual/boxes_crystals_grids.md index 1cdfb6ca2..70fdd9317 100644 --- a/docs/src/manual/boxes_crystals_grids.md +++ b/docs/src/manual/boxes_crystals_grids.md @@ -44,6 +44,66 @@ framework = apply_symmetry_rules(framework) # the framework is now in P1 and it can be used in simulations ``` +## Generating Bond Information for Frameworks + +The bonds are stored in a `SimpleGraph` from the `LightGraphs.jl` package, and +can be accessed through the `bonds` attribute. + +### Reading from a file + +`PorousMaterials` can read in bonds from `.cif` files if they have the tags +`_geom_bond_atom_site_label_1` and `_geom_bond_atom_site_label_2`. To choose to +read bonds from a file, pass `read_bonds_from_file=true` to the `Framework` +constructor. + +```julia +using PorousMaterials + +f = Framework("KAXQIL_clean.cif"; read_bonds_from_file=true, convert_to_p1=false) + +f.bonds +``` + +This example uses a structure that is not in P1 symmetry. `PorousMaterials` +cannot replicate a structure or apply symmetry rules if it currently has bonds. +However, this structure can be converted to P1 without bonds, and then bonds can +be inferred for the full P1 structure. + +### Inferring bonds using `BondingRule`s + +`PorousMaterials` can infer bonds for a structure and populate the bond graph by +using `BondingRule`s. Each `BondingRule` has two species of atoms that it works +for. It also has a minimum and maximum distance that a bond can be defined for +the two atoms. + +```julia +using PorousMaterials + +f = Framework("SBMOF-1.cif") + +# define an array of BondingRule's that will be used to define bonds in the +# framework. These need to be in the order that they are applied +bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), + BondingRule(:*, :*, 0.4, 1.9)] + +# infer the bonds for the framework f +infer_bonds!(f, bonding_rules) + +# redefine bonding_rules to account for edge cases between Ca and O atoms +bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), + BondingRule(:Ca, :O, 0.4, 2.5), + BondingRule(:*, :*, 0.4, 1.9)] + +# remove old bonds from framework before inferring bonds with new rules +remove_bonds!(f) + +# re-infer bonds +infer_bonds!(f, bonding_rules) + +# output the bond information to visualize it and double check +write_bond_information(f, "SBMOF-1_bonds.vtk") +``` + ## Building Blocks of PorousMaterials: Bravais lattice @@ -134,6 +194,7 @@ write_cube(grid, "CH4_in_SBMOF1.cube") Framework remove_overlapping_atoms_and_charges strip_numbers_from_atom_labels! + wrap_atoms_to_unit_cell chemical_formula molecular_weight crystal_density @@ -143,6 +204,11 @@ write_cube(grid, "CH4_in_SBMOF1.cube") apply_symmetry_rules is_symmetry_equal write_cif + BondingRule + write_bond_information + infer_bonds! + remove_bonds! + compare_bonds_in_framework ``` ## Grids diff --git a/src/Crystal.jl b/src/Crystal.jl index 6f8371efb..a36a15f83 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -956,6 +956,7 @@ end Used for making sure that a framework is in P1 symmetry before running simulations on it. If the structure is not in P1, this will throw an AssertionError. +-`framework::Framework`: The framework to be tested for P1 symmetry """ function assert_P1_symmetry(framework::Framework) @assert framework.is_p1 @sprintf("The framework %s is not in P1 symmetry.\n @@ -969,6 +970,9 @@ end remove_bonds!(framework) Remove all bonds from a framework structure. + +# Arguments +-`framework::Framework`: the framework that bonds wil be removed from """ function remove_bonds!(framework::Framework) while ne(framework.bonds) > 0 @@ -985,6 +989,12 @@ pair doesn't have a suitable rule then they will not be considered bonded. `:*` is considered a wildcard and can be substituted for any species. It is a good idea to include a bonding rule between two `:*` to allow any atoms to bond as long as they are close enough. + +# Arguments +-`framework::Framework`: The framework that bonds will be added to +-`bonding_rules::Array{BondingRule, 1}`: The array of bonding rules that will + be used to fill the bonding information. They are applied in the order that + they appear. """ # TODO make default bonding rules with H* and ** function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}; include_bonds_across_periodic_boundaries::Bool=true) @@ -1032,7 +1042,12 @@ end bonds_equal = compare_bonds_in_framework(framework1, framework2) Returns whether the bonds defined in framework1 are the same as the bonds -defined in framework2. +defined in framework2. It checks whether the atoms in the same positions +have the same bonds. + +# Arguments +-`framework1::Framework`: The first framework +-`framework2::Framework`: The second framework """ function compare_bonds_in_framework(fi::Framework, fj::Framework; atol::Float64=0.0) if ne(fi.bonds) != ne(fj.bonds) @@ -1159,8 +1174,17 @@ function write_cif(framework::Framework, filename::AbstractString; fractional::B end """ + write_bond_information(framework, filename) + +Writes the bond information from a framework to the selected filename. + +# Arguments +-`framework::Framework`: The framework to have its bonds written to a vtk file +-`filename::AbstractString`: The filename the bond information will be saved to """ function write_bond_information(framework::Framework, filename::AbstractString) + if ne(framework.bonds) == 0 + @warn("Framework %s has no bonds present. To get bonding information for this framework run `infer_bonds!` with an array of bonding rules\n", framework.name) if ! occursin(".vtk", filename) filename *= ".vtk" end @@ -1177,6 +1201,7 @@ function write_bond_information(framework::Framework, filename::AbstractString) @printf(vtk_file, "2\t%d\t%d\n", edge.src - 1, edge.dst - 1) end close(vtk_file) + @printf("Saving bond information for framework %s to %s.\n", framework.name, joinpath(pwd(), filename)) end """ @@ -1274,7 +1299,6 @@ function Base.show(io::IO, framework::Framework) end end -# TODO add something comparing symmetry rules function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checknames::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) @@ -1293,6 +1317,7 @@ function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checkna return box_flag && charges_flag && atoms_flag && symmetry_flag end +# TODO add bond information too function Base.:+(frameworks::Framework...; check_overlap::Bool=true) new_framework = deepcopy(frameworks[1]) for (i, f) in enumerate(frameworks) diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 14d2fadcb..8f0e90e0e 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -116,6 +116,7 @@ export replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, remove_bonds!, compare_bonds_in_framework, wrap_atoms_to_unit_cell!, + write_bond_information, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, From 2a9d348a9410835d6c3f08c0c6ef99fdd837921e Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 13 Sep 2019 17:47:14 -0700 Subject: [PATCH 079/105] add missing end statement to warning in the bond_information writer --- src/Crystal.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index a36a15f83..823d33395 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -26,6 +26,12 @@ end BondingRule(:*, :*, 0.4, 1.9)] A rule for determining if two atoms within a framework are bonded. + +# Attributes +-`species_i::Symbol`: One of the atoms types for this bond rule +-`species_j::Symbol`: The other atom type for this bond rule +-`min_dist`: The minimum distance between the atoms for bonding to occur +-`max_dist`: The maximum distance between the atoms for bonding to occur """ struct BondingRule species_i::Symbol @@ -54,7 +60,8 @@ function it is assumed it is in P1 symmetry. - `check_atom_and_charge_overlap::Bool`: throw an error if overlapping atoms are detected. - `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. - `convert_to_p1::Bool`: If the structure is not in P1 it will be converted to - P1 symmetry using the symmetry rules + P1 symmetry using the symmetry rules. The space groups name will not be + used when converting from the lower level symmetry to P1. - `read_bonds_from_file::Bool`: Whether or not to read bonding information from cif file. If false, the bonds can be inferred later @@ -72,7 +79,8 @@ function it is assumed it is in P1 symmetry. the symmetry operations. If the structure is in P1 there will be one symmetry operation. - `space_group::AbstractString`: The name of the space group. This is stored - so that it can be written out again in the write_cif function + so that it can be written out again in the write_cif function. The space + group is not used to verify the symmetry rules. - `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This is used before any simulations such as GCMC and Henry Coefficient """ @@ -1185,6 +1193,7 @@ Writes the bond information from a framework to the selected filename. function write_bond_information(framework::Framework, filename::AbstractString) if ne(framework.bonds) == 0 @warn("Framework %s has no bonds present. To get bonding information for this framework run `infer_bonds!` with an array of bonding rules\n", framework.name) + end if ! occursin(".vtk", filename) filename *= ".vtk" end From f50ae8be607a3f102ce8725ddfa3a515077beb82 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Fri, 13 Sep 2019 17:53:31 -0700 Subject: [PATCH 080/105] Add assertion for P1 symmetry to replicate because symmetry rules don't work on replicated structures --- src/Crystal.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Crystal.jl b/src/Crystal.jl index 823d33395..db1e69707 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -472,6 +472,7 @@ construct a new `Framework`. Note `replicate(framework, (1, 1, 1))` returns the function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) # TODO name the remove_bonds func @assert ne(framework.bonds) == 0 @sprintf("The framework %s has bonds within it. Remove the bonds to replicate, and then use `infer_bonds(framework)` to recalculate bond information", framework.name) + assert_P1_symmetry(framework) # determine number of atoms in replicated framework n_atoms = size(framework.atoms.xf, 2) * repfactors[1] * repfactors[2] * repfactors[3] From 566787584fedc7b66e607424574e85a0e700fe09 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 17 Sep 2019 15:31:31 -0700 Subject: [PATCH 081/105] add bonding to the addition section --- src/Crystal.jl | 9 +++++++-- test/crystal_test.jl | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index db1e69707..9d5d67975 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1327,7 +1327,6 @@ function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checkna return box_flag && charges_flag && atoms_flag && symmetry_flag end -# TODO add bond information too function Base.:+(frameworks::Framework...; check_overlap::Bool=true) new_framework = deepcopy(frameworks[1]) for (i, f) in enumerate(frameworks) @@ -1341,10 +1340,16 @@ function Base.:+(frameworks::Framework...; check_overlap::Bool=true) new_atoms = new_framework.atoms + f.atoms new_charges = new_framework.charges + f.charges + nf_n_atoms = new_framework.atoms.n_atoms + add_vertices!(new_framework.bonds, nf_n_atoms) + for edge in collect(edges(f.bonds)) + add_edge!(nf_n_atoms + edge.src, nf_n_atoms + edge.dst) + end + new_framework = Framework(split(new_framework.name, ".")[1] * "_" * split(f.name, ".")[1], new_framework.box, new_atoms, new_charges, symmetry=new_framework.symmetry,space_group=new_framework.space_group, - is_p1=new_framework.is_p1) + is_p1=new_framework.is_p1, bonds=new_framework.bonds) end if check_overlap if atom_overlap(new_framework) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 24b15d5e6..c8ce35959 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -199,6 +199,12 @@ using Random 8.0 11.0; 9.0 12.0])) f3 = f1 + f2 + addition_bonding_rules = [BondingRule(:a, :b, 5.0, 5.3), + BondingRule(:c, :d, 5.0, 5.3)] + infer_bonds!(f1, addition_bonding_rules) + infer_bonds!(f2, addition_bonding_rules) + infer_bonds!(f3, addition_bonding_rules) + @test compare_bonds_in_framework(f1 + f2, f3) @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) @test isapprox(f2.box, f3.box) From 94b7207111426000cfc74ee3ae6dbd6e6a996b09 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Tue, 17 Sep 2019 15:42:05 -0700 Subject: [PATCH 082/105] make the bonding rules an optional argument for infer_bonds./uninstall Not making it keyword so that it doesn't break other uses, and it will be common to use own bonding rules --- src/Crystal.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 9d5d67975..ca9f26cf4 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -470,8 +470,7 @@ construct a new `Framework`. Note `replicate(framework, (1, 1, 1))` returns the - `replicated_frame::Framework`: Replicated framework """ function replicate(framework::Framework, repfactors::Tuple{Int, Int, Int}) - # TODO name the remove_bonds func - @assert ne(framework.bonds) == 0 @sprintf("The framework %s has bonds within it. Remove the bonds to replicate, and then use `infer_bonds(framework)` to recalculate bond information", framework.name) + @assert ne(framework.bonds) == 0 @sprintf("The framework %s has bonds within it. Remove the bonds with `remove_bonds!` to replicate, and then use `infer_bonds(framework)` to recalculate bond information", framework.name) assert_P1_symmetry(framework) # determine number of atoms in replicated framework n_atoms = size(framework.atoms.xf, 2) * repfactors[1] * repfactors[2] * repfactors[3] @@ -1004,9 +1003,12 @@ as long as they are close enough. -`bonding_rules::Array{BondingRule, 1}`: The array of bonding rules that will be used to fill the bonding information. They are applied in the order that they appear. +-`include_bonds_across_periodic_boundaries::Bool`: Whether to check across the + periodic boundary when calculating bonds """ -# TODO make default bonding rules with H* and ** -function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}; include_bonds_across_periodic_boundaries::Bool=true) +function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}= + [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]; + include_bonds_across_periodic_boundaries::Bool=true) @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them with the `remove_bonds!` function before inferring new ones.", framework.name) # loop over every atom From 5ebdfcee70ed73d9a451f7b4e585994b827ece3c Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 18 Sep 2019 11:45:29 -0700 Subject: [PATCH 083/105] fix bond addition, add new test to make sure they aren't equal before inferring bonds on combined --- src/Crystal.jl | 2 +- test/crystal_test.jl | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index ebf9dc914..677b9c011 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1353,7 +1353,7 @@ function Base.:+(frameworks::Framework...; check_overlap::Bool=true) nf_n_atoms = new_framework.atoms.n_atoms add_vertices!(new_framework.bonds, nf_n_atoms) for edge in collect(edges(f.bonds)) - add_edge!(nf_n_atoms + edge.src, nf_n_atoms + edge.dst) + add_edge!(new_framework.bonds, nf_n_atoms + edge.src, nf_n_atoms + edge.dst) end new_framework = Framework(split(new_framework.name, ".")[1] * "_" * split(f.name, ".")[1], diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 89b1f300d..d7803daab 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -199,11 +199,12 @@ using Random 8.0 11.0; 9.0 12.0])) f3 = f1 + f2 - addition_bonding_rules = [BondingRule(:a, :b, 5.0, 5.3), - BondingRule(:c, :d, 5.0, 5.3)] - infer_bonds!(f1, addition_bonding_rules) - infer_bonds!(f2, addition_bonding_rules) - infer_bonds!(f3, addition_bonding_rules) + addition_bonding_rules = [BondingRule(:a, :b, 4.5, 5.3), + BondingRule(:c, :d, 4.5, 5.3)] + infer_bonds!(f1, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) + infer_bonds!(f2, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) + @test ! compare_bonds_in_framework(f1 + f2, f3) + infer_bonds!(f3, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) @test compare_bonds_in_framework(f1 + f2, f3) @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) From 2ce7a138124c628a95ada58c182f791e09c87adc Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 18 Sep 2019 11:53:49 -0700 Subject: [PATCH 084/105] add tests for replication P1 assertions --- test/simulation_rules_test.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/simulation_rules_test.jl b/test/simulation_rules_test.jl index c31be77b1..a2d7ee10c 100644 --- a/test/simulation_rules_test.jl +++ b/test/simulation_rules_test.jl @@ -35,5 +35,10 @@ using Test @test_throws AssertionError energy_grid(non_P1_framework, molecule, ljff) @test_throws AssertionError compute_accessibility_grid(non_P1_framework, molecule, ljff) + # Test that an assertion is thrown when trying to replicate a non-P1 + # structure + @test_throws AssertionError replicate(non_P1_framework, (2, 2, 2)) + @test_throws AssertionError replicate(non_P1_framework, (1, 1, 1)) + end end From d445438547e8a73a8b6516a6412997dce752d2bc Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 18 Sep 2019 12:20:15 -0700 Subject: [PATCH 085/105] add julia 1.2 to testing suite --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9b3234513..a2fc7668c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: julia julia: - 1.0 - 1.1 +- 1.2 addons: apt: packages: From 09d16eed229e45487c6bf3b08cf944473746cee9 Mon Sep 17 00:00:00 2001 From: Arthur York Date: Wed, 18 Sep 2019 13:32:14 -0700 Subject: [PATCH 086/105] remove julia 1.2 testing for now, add in another pr --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a2fc7668c..9b3234513 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: julia julia: - 1.0 - 1.1 -- 1.2 addons: apt: packages: From 5c3056566b232e4e5c59bce3e7dbca4bdff79da5 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Sat, 12 Oct 2019 17:03:29 -0700 Subject: [PATCH 087/105] Update some docstrings --- src/Crystal.jl | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 20a0b67b4..7b9b42ca8 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -83,8 +83,8 @@ function it is assumed it is in P1 symmetry. - `space_group::AbstractString`: The name of the space group. This is stored so that it can be written out again in the write_cif function. The space group is not used to verify the symmetry rules. -- `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. This - is used before any simulations such as GCMC and Henry Coefficient +- `is_p1::Bool`: Stores whether the framework is currently in P1 symmetry. Prior + to GCMC and Widom insertion simulations this is checked. - `wrap_to_unit_cell::Bool`: Whether the atom and charge positions will be wrapped to the unit cell so their coordinates are in [0, 1] """ @@ -542,9 +542,9 @@ write_xyz(framework::Framework; comment::AbstractString="", center::Bool=false) comment=comment, center=center) """ - is_overlap = atom_overlap(framework; overlap_tol=0.1, verbose=false) + overlap = atom_overlap(framework; overlap_tol=0.1, verbose=false) -Return true iff any two `Atoms` in the crystal overlap by calculating the distance +Return true if any two `Atoms` in the crystal overlap by calculating the distance between every pair of atoms and ensuring distance is greater than `overlap_tol`. If verbose, print the pair of atoms which are culprits. @@ -612,7 +612,8 @@ end #TODO write tests for this! one with diff elements """ - new_framework = remove_overlapping_atoms_and_charges(framework, overlap_tol=0.1, verbose=true) + new_framework = remove_overlapping_atoms_and_charges(framework, atom_overlap_tol=0.1, + charge_overlap_tol=0.1, verbose=true) Takes in a framework and returns a new framework with where overlapping atoms and overlapping charges were removed. i.e. if there is an overlapping pair, one in the pair is removed. @@ -623,6 +624,7 @@ must be identical. - `framework::Framework`: The framework containing the crystal structure information - `atom_overlap_tol::Float64`: The minimum distance between two atoms that is tolerated - `charge_overlap_tol::Float64`: The minimum distance between two charges that is tolerated +- `verbose::Bool`: Will print out information regarding the function call # Returns - `new_framework::Framework`: A new framework where identical atoms have been removed. @@ -997,7 +999,8 @@ function remove_bonds!(framework::Framework) end """ - infer_bonds!(framework, bonding_rules) + infer_bonds!(framework, bonding_rules=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)], + include_bonds_across_periodic_boundaries=true) Populate the bonds in the framework object based on the bonding rules. If a pair doesn't have a suitable rule then they will not be considered bonded. @@ -1006,6 +1009,8 @@ pair doesn't have a suitable rule then they will not be considered bonded. good idea to include a bonding rule between two `:*` to allow any atoms to bond as long as they are close enough. +The bonding rules are hierarchical, i.e. the first bonding rule takes precedence over the latter ones. + # Arguments -`framework::Framework`: The framework that bonds will be added to -`bonding_rules::Array{BondingRule, 1}`: The array of bonding rules that will @@ -1058,7 +1063,7 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} end """ - bonds_equal = compare_bonds_in_framework(framework1, framework2) + bonds_equal = compare_bonds_in_framework(framework1, framework2, atol=0.0) Returns whether the bonds defined in framework1 are the same as the bonds defined in framework2. It checks whether the atoms in the same positions @@ -1067,6 +1072,10 @@ have the same bonds. # Arguments -`framework1::Framework`: The first framework -`framework2::Framework`: The second framework +-`atol::Float64`: absolute tolerance for the comparison of coordinates in the framework + +# Returns +-`bonds_equal::Bool`: Wether the bonds in framework1 and framework2 are equal """ function compare_bonds_in_framework(fi::Framework, fj::Framework; atol::Float64=0.0) if ne(fi.bonds) != ne(fj.bonds) From 953e7bc2bba8971e49d07eb46dbf5aa094e65e06 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Sat, 12 Oct 2019 17:21:19 -0700 Subject: [PATCH 088/105] Reorder few if-statements --- src/Crystal.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 7b9b42ca8..18cd65fa0 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -183,10 +183,10 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, haskey(name_to_column, "_atom_site_fract_y") && haskey(name_to_column, "_atom_site_fract_z") # if the file provides cartesian coordinates - cartesian = cartesian || haskey(name_to_column, "_atom_site_Cartn_x") && + cartesian = cartesian || ! fractional && haskey(name_to_column, "_atom_site_Cartn_x") && haskey(name_to_column, "_atom_site_Cartn_y") && - haskey(name_to_column, "_atom_site_Cartn_z") && - ! fractional # if both are provided, will default + haskey(name_to_column, "_atom_site_Cartn_z") + # if both are provided, will default # to using fractional, so keep cartesian # false @@ -304,9 +304,9 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # ===================== # BOND READER # ===================== - elseif haskey(name_to_column, "_geom_bond_atom_site_label_1") && - haskey(name_to_column, "_geom_bond_atom_site_label_2") && - read_bonds_from_file + elseif read_bonds_from_file && + haskey(name_to_column, "_geom_bond_atom_site_label_1") && + haskey(name_to_column, "_geom_bond_atom_site_label_2") while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) From c7edf958e8fbfbd9603469072a3eb5dc62aca407 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Sat, 12 Oct 2019 17:28:03 -0700 Subject: [PATCH 089/105] Changed a for loop to a list comprehension --- src/Crystal.jl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 18cd65fa0..43a20667b 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -230,12 +230,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # ===================== elseif fractional && ! atom_info atom_info = true - atom_column_name = "" - for (name, column) in name_to_column - if column == 1 - atom_column_name = name - end - end + atom_column_name = [name for (name, column) in name_to_column if column == 1][end] while i <= length(lines) && length(split(lines[i])) == length(name_to_column) line = split(lines[i]) From 8a45ecabb14a0a13c730a53533430ac9aef5cc5e Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Sat, 12 Oct 2019 17:28:54 -0700 Subject: [PATCH 090/105] Removed unnecessary checks --- src/Crystal.jl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 43a20667b..2f2835ea8 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -407,12 +407,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, framework = Framework(filename, box, atoms, charges; bonds=bonds, symmetry=symmetry_rules, space_group=space_group, is_p1=p1_symmetry) - if wrap_to_unit_cell - framework.atoms.xf .= mod.(framework.atoms.xf, 1.0) - framework.charges.xf .= mod.(framework.charges.xf, 1.0) - end - strip_numbers_from_atom_labels!(framework) - if check_charge_neutrality if ! charge_neutral(framework, net_charge_tol) From 77e8e699a569b517f07b3881a664257cfd78163c Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 16:22:35 -0700 Subject: [PATCH 091/105] is_bonded now checks to see if two atoms are connected with a BondingRule --- src/Crystal.jl | 68 ++++++++++++++++++++++++------------------ src/PorousMaterials.jl | 2 +- test/crystal_test.jl | 2 ++ 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 2f2835ea8..7e566faa1 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1017,35 +1017,8 @@ function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1} for i in 1:framework.atoms.n_atoms # loop over every unique pair of atoms for j in i+1:framework.atoms.n_atoms - # loop over possible bonding rules - for br in bonding_rules - # determine if the types are correct - # anything goes, reached the final bonding rule (if set up right) - species_match = false - if br.species_i == :* && br.species_j == :* - species_match = true - elseif br.species_i == :* && (framework.atoms.species[i] == br.species_j || - framework.atoms.species[j] == br.species_j) - species_match = true - elseif br.species_j == :* && (framework.atoms.species[i] == br.species_i || - framework.atoms.species[j] == br.species_i) - species_match = true - elseif (framework.atoms.species[i] == br.species_i && framework.atoms.species[j] == br.species_j) || - (framework.atoms.species[j] == br.species_i && framework.atoms.species[i] == br.species_j) - species_match = true - end - if species_match - # determine if they are within range - dxf = framework.atoms.xf[:, i] - framework.atoms.xf[:, j] - if include_bonds_across_periodic_boundaries - nearest_image!(dxf) - end - norm_c = norm(framework.box.f_to_c * dxf) - if br.min_dist < norm_c && norm_c < br.max_dist - add_edge!(framework.bonds, i, j) - break - end - end + if is_bonded(framework, i, j, bonding_rules; include_bonds_across_periodic_boundaries=include_bonds_across_periodic_boundaries) + add_edge!(framework.bonds, i, j) end end end @@ -1304,6 +1277,43 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 return new_framework end +function is_bonded(framework::Framework, i::Int64, j::Int64, + bonding_rules::Array{BondingRule, 1}=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]; + include_bonds_across_periodic_boundaries::Bool=true) + species_i = framework.atoms.species[i] + species_j = framework.atoms.species[j] + x_i = framework.atoms.xf[:, i] + x_j = framework.atoms.xf[:, j] + # loop over possible bonding rules + for br in bonding_rules + # determine if the atom species correspond to the species in `bonding_rules` + species_match = false + if br.species_i == :* && br.species_j == :* + species_match = true + elseif br.species_i == :* && (species_i == br.species_j || species_j == br.species_j) + species_match = true + elseif br.species_j == :* && (species_i == br.species_i || species_j == br.species_j) + species_match = true + elseif (species_i == br.species_i && species_j == br.species_j) || (species_j == br.species_i && species_i == br.species_j) + species_match = true + end + + if species_match + # determine if the atoms are close enough to bond + dxf = x_i - x_j + if include_bonds_across_periodic_boundaries + nearest_image!(dxf) + end + cartesian_dist_between_atoms = norm(framework.box.f_to_c * dxf) + if br.min_dist < cartesian_dist_between_atoms && br.max_dist > cartesian_dist_between_atoms + return true + end + end + end + return false +end + + function Base.show(io::IO, framework::Framework) println(io, "Name: ", framework.name) println(io, framework.box) diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index be5cb2470..421862fb7 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -119,7 +119,7 @@ export replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, remove_bonds!, compare_bonds_in_framework, wrap_atoms_to_unit_cell!, - write_bond_information, + write_bond_information, is_bonded, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, diff --git a/test/crystal_test.jl b/test/crystal_test.jl index d7803daab..887a4ff1f 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -201,6 +201,8 @@ using Random f3 = f1 + f2 addition_bonding_rules = [BondingRule(:a, :b, 4.5, 5.3), BondingRule(:c, :d, 4.5, 5.3)] + @test is_bonded(f1, 1, 2, [BondingRule(:a, :b, 1.0, 5.5)]; include_bonds_across_periodic_boundaries=false) + @test ! is_bonded(f2, 1, 2, [BondingRule(:c, :d, 1.0, 4.5)]; include_bonds_across_periodic_boundaries=false) infer_bonds!(f1, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) infer_bonds!(f2, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) @test ! compare_bonds_in_framework(f1 + f2, f3) From 7d725921caefad08bac7e2cc60579caf6df6dbcc Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 16:23:29 -0700 Subject: [PATCH 092/105] Remove strip numbers from atom labels in framework creation --- src/Crystal.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 7e566faa1..034c20ab1 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -417,7 +417,6 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end end - strip_numbers_from_atom_labels!(framework) if wrap_to_unit_cell wrap_atoms_to_unit_cell!(framework) end From 410021549194fa12c560e2d4d3c3f8ea8989cce1 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 16:50:22 -0700 Subject: [PATCH 093/105] Add default bonding rule function and add docstrings --- src/Crystal.jl | 36 ++++++++++++++++++++++++++++++++++++ src/PorousMaterials.jl | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 034c20ab1..704f54326 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -40,6 +40,22 @@ struct BondingRule max_dist::Float64 end +""" + default_bondingrules = default_bondingrules() + +Returns the default bonding rules. Using `append!` and/or `prepend!` to add to the default bonding rules: + +# Example +``` +bond_rules = default_bondingrules() +prepend!(bond_rules, BondingRule(:Cu, :*, 0.1, 2.6)) +``` + +# Returns +-`default_bondingrules::Array{BondingRule, 1}`: The default bonding rules: `[BondingRule(:*, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]` +""" +default_bondingrules() = [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)] + """ framework = Framework(filename, check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, @@ -1276,6 +1292,26 @@ function assign_charges(framework::Framework, charges::Union{Dict{Symbol, Float6 return new_framework end +""" + are_atoms_bonded = is_bonded(framework, i, j, bonding_rules=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)], + include_bonds_across_periodic_boundaries=true) + +Checks to see if atoms `i` and `j` in `framework` are bonded according to the `bonding_rules`. + +# Arguments +-`framework::Framework`: The framework that bonds will be added to +-`i::Int`: Index of the first atom +-`j::Int`: Index of the second atom +-`bonding_rules::Array{BondingRule, 1}`: The array of bonding rules that will + be used to fill the bonding information. They are applied in the order that + they appear. +-`include_bonds_across_periodic_boundaries::Bool`: Whether to check across the + periodic boundary when calculating bonds + +# Returns +-`are_atoms_bonded::Bool`: Whether atoms `i` and `j` are bonded according to `bonding_rules` + +""" function is_bonded(framework::Framework, i::Int64, j::Int64, bonding_rules::Array{BondingRule, 1}=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]; include_bonds_across_periodic_boundaries::Bool=true) diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index 421862fb7..ce96dd585 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -119,7 +119,7 @@ export replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, remove_bonds!, compare_bonds_in_framework, wrap_atoms_to_unit_cell!, - write_bond_information, is_bonded, + write_bond_information, is_bonded, default_bondingrules, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, From 760fde9e0107f5de1171204e363e50e723105ff7 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 17:29:11 -0700 Subject: [PATCH 094/105] Adjustments after removing strip_numbers_from_atom_labels from framework creation --- test/crystal_test.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 887a4ff1f..6ab289fb7 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -33,8 +33,10 @@ using Random # more write_cif tests below with symmetry tests write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2.cif")) framework_rewritten = Framework("rewritten_test_structure2.cif") + strip_numbers_from_atom_labels!(framework_rewritten) write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2_cartn.cif"); fractional=false) framework_rewritten_cartn = Framework("rewritten_test_structure2_cartn.cif") + strip_numbers_from_atom_labels!(framework_rewritten_cartn) @test isapprox(framework, framework_rewritten) @test isapprox(framework, framework_rewritten_cartn) @@ -43,8 +45,11 @@ using Random # should place atoms in the same positions as the P1 conversion using # openBabel non_P1_framework = Framework("symmetry_test_structure.cif") + strip_numbers_from_atom_labels!(non_P1_framework) non_P1_cartesian = Framework("symmetry_test_structure_cartn.cif") + strip_numbers_from_atom_labels!(non_P1_cartesian) P1_framework = Framework("symmetry_test_structure_P1.cif") + strip_numbers_from_atom_labels!(P1_framework) # wrap all atoms and charges to be within the unit cell non_P1_framework.atoms.xf .= mod.(non_P1_framework.atoms.xf, 1.0) @@ -73,7 +78,9 @@ using Random # test reading in non-P1 then applying symmetry later # read in the same files as above, then convert to P1, then compare non_P1_framework_symmetry = Framework("symmetry_test_structure.cif", convert_to_p1=false) + strip_numbers_from_atom_labels!(non_P1_framework_symmetry) non_P1_cartesian_symmetry = Framework("symmetry_test_structure_cartn.cif", convert_to_p1=false) + strip_numbers_from_atom_labels!(non_P1_cartesian_symmetry) # make sure these frameworks are not in P1 symmetry when convert_to_p1 is # set to false @@ -85,7 +92,9 @@ using Random # keep this in cartesian to test both write_cif(non_P1_cartesian_symmetry, joinpath("data", "crystals", "rewritten_symmetry_test_structure_cartn.cif"), fractional=false) rewritten_non_p1_fractional = Framework("rewritten_symmetry_test_structure.cif"; convert_to_p1=false) + strip_numbers_from_atom_labels!(rewritten_non_p1_fractional) rewritten_non_p1_cartesian = Framework("rewritten_symmetry_test_structure_cartn.cif"; convert_to_p1=false) + strip_numbers_from_atom_labels!(rewritten_non_p1_cartesian) @test isapprox(rewritten_non_p1_fractional, non_P1_framework_symmetry) @test isapprox(rewritten_non_p1_cartesian, non_P1_cartesian_symmetry) @@ -116,10 +125,12 @@ using Random # test replicate framework sbmof = Framework("SBMOF-1.cif") + strip_numbers_from_atom_labels!(sbmof) replicated_sbmof = replicate(sbmof, (1, 1, 1)) @test isapprox(sbmof, replicated_sbmof) # test replication no bonds assertion sbmof_bonds = Framework("SBMOF-1.cif") + strip_numbers_from_atom_labels!(sbmof_bonds) bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:Ca, :O, 0.4, 2.5), BondingRule(:*, :*, 0.4, 1.9)] @@ -128,12 +139,14 @@ using Random # write out and compare to inferred bonds write_cif(sbmof_bonds, joinpath(pwd(), "data", "crystals", "SBMOF-1_inferred_bonds.cif")) sbmof_inferred_bonds = Framework("SBMOF-1_inferred_bonds.cif"; read_bonds_from_file=true) + strip_numbers_from_atom_labels!(sbmof_inferred_bonds) @test compare_bonds_in_framework(sbmof_bonds, sbmof_inferred_bonds) # other bond info tests # TODO find more robust test/confirm these are the correct numbers # replacing this test with the one below comparing pdb bond info to inferred # bond info sbmof_bonds_copy = Framework("SBMOF-1.cif") + strip_numbers_from_atom_labels!(sbmof_bonds_copy) # reverse the order of the atoms and bond info should still be the same sbmof_bonds_copy.atoms.xf .= reverse(sbmof_bonds_copy.atoms.xf; dims=2) sbmof_bonds_copy.atoms.species .= reverse(sbmof_bonds_copy.atoms.species) @@ -145,9 +158,11 @@ using Random # test reading in bonds as part of `Framework()` sbmof_read_bonds = Framework("test_bond_viz.cif"; read_bonds_from_file=true, check_atom_and_charge_overlap=false) + strip_numbers_from_atom_labels!(sbmof_read_bonds) @test ne(sbmof_read_bonds.bonds) == 5 write_cif(sbmof_read_bonds, joinpath(pwd(), "data", "crystals", "rewritten_sbmof_read_bonds.cif")) reloaded_sbmof_read_bonds = Framework("rewritten_sbmof_read_bonds.cif"; read_bonds_from_file=true, check_atom_and_charge_overlap=false) + strip_numbers_from_atom_labels!(reloaded_sbmof_read_bonds) @test compare_bonds_in_framework(sbmof_read_bonds, reloaded_sbmof_read_bonds) # Test that reading in bonding information is the same as inferring the @@ -155,7 +170,9 @@ using Random # Bonding information is from a pdb file saved by avogadro # using non-p1 because it meant copying over fewer bonds read_bonds = Framework("KAXQIL_clean_cartn.cif"; convert_to_p1=false, read_bonds_from_file=true) + strip_numbers_from_atom_labels!(read_bonds) inferred_bonds = Framework("KAXQIL_clean.cif"; convert_to_p1=false) + strip_numbers_from_atom_labels!(inferred_bonds) # Using same bonding rules as above infer_bonds!(inferred_bonds, bonding_rules) @test compare_bonds_in_framework(read_bonds, inferred_bonds; atol=1e-6) From fc847ab197e2e12df90a2e0adee11d2b6c99bc97 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 17:43:12 -0700 Subject: [PATCH 095/105] Replace faulty FIQCEN file --- test/data/crystals/FIQCEN_clean.cif | 180 +++++++++++++++++ .../crystals/FIQCEN_clean_min_charges.cif | 185 ------------------ test/henry_checkpoint_test.jl | 2 +- 3 files changed, 181 insertions(+), 186 deletions(-) create mode 100755 test/data/crystals/FIQCEN_clean.cif delete mode 100644 test/data/crystals/FIQCEN_clean_min_charges.cif diff --git a/test/data/crystals/FIQCEN_clean.cif b/test/data/crystals/FIQCEN_clean.cif new file mode 100755 index 000000000..d7d2adad3 --- /dev/null +++ b/test/data/crystals/FIQCEN_clean.cif @@ -0,0 +1,180 @@ +data_FIQCEN_clean +_audit_creation_date 2014-07-02 +_audit_creation_method 'Materials Studio' +_symmetry_space_group_name_H-M 'P1' +_symmetry_Int_Tables_number 1 +_symmetry_cell_setting triclinic +loop_ +_symmetry_equiv_pos_as_xyz + x,y,z +_cell_length_a 18.6273 +_cell_length_b 18.6273 +_cell_length_c 18.6273 +_cell_angle_alpha 60.0000 +_cell_angle_beta 60.0000 +_cell_angle_gamma 60.0000 +loop_ +_atom_site_label +_atom_site_type_symbol +_atom_site_fract_x +_atom_site_fract_y +_atom_site_fract_z +_atom_site_U_iso_or_equiv +_atom_site_adp_type +_atom_site_occupancy +Cu1 Cu 0.57057 -0.00000 0.42943 0.01267 Uiso 1.00 +Cu2 Cu 0.00000 0.57057 0.00000 0.01267 Uiso 1.00 +Cu3 Cu 0.42943 0.00000 0.57057 0.01267 Uiso 1.00 +Cu4 Cu 0.00000 0.42943 0.00000 0.01267 Uiso 1.00 +Cu5 Cu 0.57057 0.42943 -0.00000 0.01267 Uiso 1.00 +Cu6 Cu 0.00000 1.00000 0.42943 0.01267 Uiso 1.00 +Cu7 Cu 0.42943 0.57057 -0.00000 0.01267 Uiso 1.00 +Cu8 Cu 0.00000 0.00000 0.57057 0.01267 Uiso 1.00 +Cu9 Cu 0.57057 1.00000 -0.00000 0.01267 Uiso 1.00 +Cu10 Cu 0.00000 0.42943 0.57057 0.01267 Uiso 1.00 +Cu11 Cu 0.42943 1.00000 0.00000 0.01267 Uiso 1.00 +Cu12 Cu 0.00000 0.57057 0.42943 0.01267 Uiso 1.00 +H1 H 0.72800 0.72800 0.51160 0.01267 Uiso 1.00 +H2 H 0.72800 0.72800 0.03240 0.01267 Uiso 1.00 +H3 H 0.51160 0.03240 0.72800 0.01267 Uiso 1.00 +H4 H 0.03240 0.51160 0.72800 0.01267 Uiso 1.00 +H5 H 0.72800 0.51160 0.03240 0.01267 Uiso 1.00 +H6 H 0.72800 0.03240 0.51160 0.01267 Uiso 1.00 +H7 H 0.51160 0.72800 0.72800 0.01267 Uiso 1.00 +H8 H 0.03240 0.72800 0.72800 0.01267 Uiso 1.00 +H9 H 0.72800 0.03240 0.72800 0.01267 Uiso 1.00 +H10 H 0.72800 0.51160 0.72800 0.01267 Uiso 1.00 +H11 H 0.51160 0.72800 0.03240 0.01267 Uiso 1.00 +H12 H 0.03240 0.72800 0.51160 0.01267 Uiso 1.00 +H13 H 0.27200 0.27200 0.48840 0.01267 Uiso 1.00 +H14 H 0.27200 0.27200 0.96760 0.01267 Uiso 1.00 +H15 H 0.96760 0.48840 0.27200 0.01267 Uiso 1.00 +H16 H 0.48840 0.96760 0.27200 0.01267 Uiso 1.00 +H17 H 0.48840 0.27200 0.96760 0.01267 Uiso 1.00 +H18 H 0.96760 0.27200 0.48840 0.01267 Uiso 1.00 +H19 H 0.27200 0.48840 0.27200 0.01267 Uiso 1.00 +H20 H 0.27200 0.96760 0.27200 0.01267 Uiso 1.00 +H21 H 0.96760 0.27200 0.27200 0.01267 Uiso 1.00 +H22 H 0.48840 0.27200 0.27200 0.01267 Uiso 1.00 +H23 H 0.27200 0.48840 0.96760 0.01267 Uiso 1.00 +H24 H 0.27200 0.96760 0.48840 0.01267 Uiso 1.00 +C1 C 0.25700 0.96900 0.38700 0.01267 Uiso 1.00 +C2 C 0.96900 0.25700 0.38700 0.01267 Uiso 1.00 +C3 C 0.38700 0.38700 0.25700 0.01267 Uiso 1.00 +C4 C 0.38700 0.38700 0.96900 0.01267 Uiso 1.00 +C5 C 0.25700 0.38700 0.38700 0.01267 Uiso 1.00 +C6 C 0.96900 0.38700 0.38700 0.01267 Uiso 1.00 +C7 C 0.38700 0.25700 0.96900 0.01267 Uiso 1.00 +C8 C 0.38700 0.96900 0.25700 0.01267 Uiso 1.00 +C9 C 0.25700 0.38700 0.96900 0.01267 Uiso 1.00 +C10 C 0.96900 0.38700 0.25700 0.01267 Uiso 1.00 +C11 C 0.38700 0.96900 0.38700 0.01267 Uiso 1.00 +C12 C 0.38700 0.25700 0.38700 0.01267 Uiso 1.00 +C13 C 0.03100 0.74300 0.61300 0.01267 Uiso 1.00 +C14 C 0.74300 0.03100 0.61300 0.01267 Uiso 1.00 +C15 C 0.61300 0.61300 0.74300 0.01267 Uiso 1.00 +C16 C 0.61300 0.61300 0.03100 0.01267 Uiso 1.00 +C17 C 0.61300 0.74300 0.61300 0.01267 Uiso 1.00 +C18 C 0.61300 0.03100 0.61300 0.01267 Uiso 1.00 +C19 C 0.74300 0.61300 0.03100 0.01267 Uiso 1.00 +C20 C 0.03100 0.61300 0.74300 0.01267 Uiso 1.00 +C21 C 0.61300 0.74300 0.03100 0.01267 Uiso 1.00 +C22 C 0.61300 0.03100 0.74300 0.01267 Uiso 1.00 +C23 C 0.03100 0.61300 0.61300 0.01267 Uiso 1.00 +C24 C 0.74300 0.61300 0.61300 0.01267 Uiso 1.00 +C25 C 0.56870 0.56870 0.83770 0.01267 Uiso 1.00 +C26 C 0.56870 0.56870 0.02490 0.01267 Uiso 1.00 +C27 C 0.83770 0.02490 0.56870 0.01267 Uiso 1.00 +C28 C 0.02490 0.83770 0.56870 0.01267 Uiso 1.00 +C29 C 0.56870 0.83770 0.02490 0.01267 Uiso 1.00 +C30 C 0.56870 0.02490 0.83770 0.01267 Uiso 1.00 +C31 C 0.83770 0.56870 0.56870 0.01267 Uiso 1.00 +C32 C 0.02490 0.56870 0.56870 0.01267 Uiso 1.00 +C33 C 0.56870 0.02490 0.56870 0.01267 Uiso 1.00 +C34 C 0.56870 0.83770 0.56870 0.01267 Uiso 1.00 +C35 C 0.83770 0.56870 0.02490 0.01267 Uiso 1.00 +C36 C 0.02490 0.56870 0.83770 0.01267 Uiso 1.00 +C37 C 0.43130 0.43130 0.16230 0.01267 Uiso 1.00 +C38 C 0.43130 0.43130 0.97510 0.01267 Uiso 1.00 +C39 C 0.97510 0.16230 0.43130 0.01267 Uiso 1.00 +C40 C 0.16230 0.97510 0.43130 0.01267 Uiso 1.00 +C41 C 0.16230 0.43130 0.97510 0.01267 Uiso 1.00 +C42 C 0.97510 0.43130 0.16230 0.01267 Uiso 1.00 +C43 C 0.43130 0.16230 0.43130 0.01267 Uiso 1.00 +C44 C 0.43130 0.97510 0.43130 0.01267 Uiso 1.00 +C45 C 0.97510 0.43130 0.43130 0.01267 Uiso 1.00 +C46 C 0.16230 0.43130 0.43130 0.01267 Uiso 1.00 +C47 C 0.43130 0.16230 0.97510 0.01267 Uiso 1.00 +C48 C 0.43130 0.97510 0.16230 0.01267 Uiso 1.00 +C49 C 0.30060 0.96840 0.43040 0.01267 Uiso 1.00 +C50 C 0.96840 0.30060 0.30060 0.01267 Uiso 1.00 +C51 C 0.43040 0.30060 0.30060 0.01267 Uiso 1.00 +C52 C 0.30060 0.43040 0.96840 0.01267 Uiso 1.00 +C53 C 0.30060 0.43040 0.30060 0.01267 Uiso 1.00 +C54 C 0.96840 0.30060 0.43040 0.01267 Uiso 1.00 +C55 C 0.43040 0.30060 0.96840 0.01267 Uiso 1.00 +C56 C 0.30060 0.96840 0.30060 0.01267 Uiso 1.00 +C57 C 0.30060 0.30060 0.96840 0.01267 Uiso 1.00 +C58 C 0.96840 0.43040 0.30060 0.01267 Uiso 1.00 +C59 C 0.43040 0.96840 0.30060 0.01267 Uiso 1.00 +C60 C 0.30060 0.30060 0.43040 0.01267 Uiso 1.00 +C61 C 0.03160 0.69940 0.56960 0.01267 Uiso 1.00 +C62 C 0.69940 0.03160 0.69940 0.01267 Uiso 1.00 +C63 C 0.69940 0.56960 0.69940 0.01267 Uiso 1.00 +C64 C 0.56960 0.69940 0.03160 0.01267 Uiso 1.00 +C65 C 0.56960 0.69940 0.69940 0.01267 Uiso 1.00 +C66 C 0.69940 0.03160 0.56960 0.01267 Uiso 1.00 +C67 C 0.69940 0.56960 0.03160 0.01267 Uiso 1.00 +C68 C 0.03160 0.69940 0.69940 0.01267 Uiso 1.00 +C69 C 0.69940 0.69940 0.03160 0.01267 Uiso 1.00 +C70 C 0.56960 0.03160 0.69940 0.01267 Uiso 1.00 +C71 C 0.03160 0.56960 0.69940 0.01267 Uiso 1.00 +C72 C 0.69940 0.69940 0.56960 0.01267 Uiso 1.00 +O1 O 0.61201 0.49247 0.87425 0.01267 Uiso 1.00 +O2 O 0.49247 0.61201 0.02127 0.01267 Uiso 1.00 +O3 O 0.87425 0.02127 0.61201 0.01267 Uiso 1.00 +O4 O 0.02127 0.87425 0.49247 0.01267 Uiso 1.00 +O5 O 0.61201 0.87425 0.02127 0.01267 Uiso 1.00 +O6 O 0.49247 0.02127 0.87425 0.01267 Uiso 1.00 +O7 O 0.87425 0.61201 0.49247 0.01267 Uiso 1.00 +O8 O 0.02127 0.49247 0.61201 0.01267 Uiso 1.00 +O9 O 0.61201 0.02127 0.49247 0.01267 Uiso 1.00 +O10 O 0.49247 0.87425 0.61201 0.01267 Uiso 1.00 +O11 O 0.87425 0.49247 0.02127 0.01267 Uiso 1.00 +O12 O 0.02127 0.61201 0.87425 0.01267 Uiso 1.00 +O13 O 0.50753 0.38799 0.12575 0.01267 Uiso 1.00 +O14 O 0.38799 0.50753 0.97873 0.01267 Uiso 1.00 +O15 O 0.97873 0.12575 0.38799 0.01267 Uiso 1.00 +O16 O 0.12575 0.97873 0.50753 0.01267 Uiso 1.00 +O17 O 0.12575 0.38799 0.97873 0.01267 Uiso 1.00 +O18 O 0.97873 0.50753 0.12575 0.01267 Uiso 1.00 +O19 O 0.38799 0.12575 0.50753 0.01267 Uiso 1.00 +O20 O 0.50753 0.97873 0.38799 0.01267 Uiso 1.00 +O21 O 0.97873 0.38799 0.50753 0.01267 Uiso 1.00 +O22 O 0.12575 0.50753 0.38799 0.01267 Uiso 1.00 +O23 O 0.50753 0.12575 0.97873 0.01267 Uiso 1.00 +O24 O 0.38799 0.97873 0.12575 0.01267 Uiso 1.00 +O25 O 0.38799 0.50753 0.12575 0.01267 Uiso 1.00 +O26 O 0.50753 0.38799 0.97873 0.01267 Uiso 1.00 +O27 O 0.12575 0.97873 0.38799 0.01267 Uiso 1.00 +O28 O 0.97873 0.12575 0.50753 0.01267 Uiso 1.00 +O29 O 0.38799 0.12575 0.97873 0.01267 Uiso 1.00 +O30 O 0.50753 0.97873 0.12575 0.01267 Uiso 1.00 +O31 O 0.12575 0.38799 0.50753 0.01267 Uiso 1.00 +O32 O 0.97873 0.50753 0.38799 0.01267 Uiso 1.00 +O33 O 0.38799 0.97873 0.50753 0.01267 Uiso 1.00 +O34 O 0.50753 0.12575 0.38799 0.01267 Uiso 1.00 +O35 O 0.12575 0.50753 0.97873 0.01267 Uiso 1.00 +O36 O 0.97873 0.38799 0.12575 0.01267 Uiso 1.00 +O37 O 0.49247 0.61201 0.87425 0.01267 Uiso 1.00 +O38 O 0.61201 0.49247 0.02127 0.01267 Uiso 1.00 +O39 O 0.02127 0.87425 0.61201 0.01267 Uiso 1.00 +O40 O 0.87425 0.02127 0.49247 0.01267 Uiso 1.00 +O41 O 0.87425 0.61201 0.02127 0.01267 Uiso 1.00 +O42 O 0.02127 0.49247 0.87425 0.01267 Uiso 1.00 +O43 O 0.61201 0.87425 0.49247 0.01267 Uiso 1.00 +O44 O 0.49247 0.02127 0.61201 0.01267 Uiso 1.00 +O45 O 0.02127 0.61201 0.49247 0.01267 Uiso 1.00 +O46 O 0.87425 0.49247 0.61201 0.01267 Uiso 1.00 +O47 O 0.49247 0.87425 0.02127 0.01267 Uiso 1.00 +O48 O 0.61201 0.02127 0.87425 0.01267 Uiso 1.00 diff --git a/test/data/crystals/FIQCEN_clean_min_charges.cif b/test/data/crystals/FIQCEN_clean_min_charges.cif deleted file mode 100644 index 0767e85b6..000000000 --- a/test/data/crystals/FIQCEN_clean_min_charges.cif +++ /dev/null @@ -1,185 +0,0 @@ -#====================================================================== - -# CRYSTAL DATA - -#---------------------------------------------------------------------- - -data_VESTA_phase_1 - - -_pd_phase_name MOF -_cell_length_a 18.74300 -_cell_length_b 18.76500 -_cell_length_c 18.76200 -_cell_angle_alpha 59.98000 -_cell_angle_beta 60.08300 -_cell_angle_gamma 60.10900 -_symmetry_space_group_name_H-M "P 1" -_symmetry_Int_Tables_number 1 - -loop_ -_symmetry_equiv_pos_as_xyz - "x, y, z" - -loop_ - _atom_site_label - _atom_site_fract_x - _atom_site_fract_y - _atom_site_fract_z - _atom_site_charge - Cu1 0.565372 0.000174 0.43460 0.936049294872 - Cu1 0.000235 0.565345 0.99996 0.936049294872 - Cu1 0.434599 0.999819 0.56541 0.936049294872 - Cu1 0.999705 0.434699 8.1e-0 0.936049294872 - Cu1 0.565375 0.434645 0.00013 0.936049294872 - Cu1 0.999771 6.2e-05 0.4346 0.936049294872 - Cu1 0.434607 0.565305 0.9998 0.936049294872 - Cu1 0.000199 0.999914 0.56538 0.936049294872 - Cu1 0.565388 9.1e-05 7.8e-0 0.936049294872 - Cu1 0.999913 0.434586 0.565 0.936049294872 - Cu1 0.434519 0.99988 0.99996 0.936049294872 - Cu1 4.4e-05 0.565377 0.43451 0.936049294872 - H1 0.734134 0.734163 0.50424 0.114709294872 - H1 0.734064 0.734487 0.0273 0.114709294872 - H1 0.504265 0.027895 0.73400 0.114709294872 - H1 0.027289 0.504629 0.73401 0.114709294872 - H1 0.734049 0.505152 0.02700 0.114709294872 - H1 0.734177 0.026584 0.50518 0.114709294872 - H1 0.50476 0.733824 0.73365 0.114709294872 - H1 0.028097 0.733942 0.73364 0.114709294872 - H1 0.734049 0.02771 0.73423 0.114709294872 - H1 0.734257 0.504742 0.73405 0.114709294872 - H1 0.504338 0.733979 0.02771 0.114709294872 - H1 0.028018 0.733945 0.50412 0.114709294872 - H1 0.265827 0.26583 0.49577 0.114709294872 - H1 0.265807 0.265538 0.97267 0.114709294872 - H1 0.972646 0.49535 0.26605 0.114709294872 - H1 0.495719 0.972071 0.26597 0.114709294872 - H1 0.495633 0.265874 0.97229 0.114709294872 - H1 0.97194 0.26599 0.49590 0.114709294872 - H1 0.265742 0.495204 0.26591 0.114709294872 - H1 0.265919 0.972219 0.26582 0.114709294872 - H1 0.971869 0.266065 0.2663 0.114709294872 - H1 0.49523 0.266113 0.26639 0.114709294872 - H1 0.266005 0.494784 0.97303 0.114709294872 - H1 0.265833 0.973392 0.49482 0.114709294872 - C1 0.256691 0.971502 0.38557 -0.170020705128 - C1 0.97067 0.256844 0.38632 -0.170020705128 - C1 0.385733 0.385918 0.25691 -0.170020705128 - C1 0.386069 0.385604 0.97132 -0.170020705128 - C1 0.256608 0.385792 0.38610 -0.170020705128 - C1 0.970887 0.385957 0.38624 -0.170020705128 - C1 0.38592 0.256519 0.97127 -0.170020705128 - C1 0.386037 0.970908 0.25670 -0.170020705128 - C1 0.256702 0.385432 0.97152 -0.170020705128 - C1 0.970944 0.385983 0.25697 -0.170020705128 - C1 0.386037 0.971371 0.38567 -0.170020705128 - C1 0.385749 0.256766 0.38629 -0.170020705128 - C1 0.029296 0.743126 0.61368 -0.170020705128 - C1 0.743301 0.028449 0.61445 -0.170020705128 - C1 0.614256 0.614029 0.74308 -0.170020705128 - C1 0.61394 0.614298 0.02870 -0.170020705128 - C1 0.614222 0.743203 0.61372 -0.170020705128 - C1 0.613962 0.028602 0.61431 -0.170020705128 - C1 0.743261 0.614553 0.02851 -0.170020705128 - C1 0.029006 0.614014 0.74305 -0.170020705128 - C1 0.614 0.743424 0.02876 -0.170020705128 - C1 0.613941 0.029042 0.74331 -0.170020705128 - C1 0.029057 0.614 0.61380 -0.170020705128 - C1 0.743368 0.614181 0.61388 -0.170020705128 - C2 0.570015 0.569768 0.83672 0.692409294872 - C2 0.569676 0.569984 0.02361 0.692409294872 - C2 0.836951 0.023115 0.570 0.692409294872 - C2 0.024044 0.836744 0.56946 0.692409294872 - C2 0.569789 0.836929 0.02358 0.692409294872 - C2 0.569723 0.023923 0.83683 0.692409294872 - C2 0.836882 0.56996 0.5696 0.692409294872 - C2 0.023885 0.569762 0.56960 0.692409294872 - C2 0.569727 0.023395 0.57008 0.692409294872 - C2 0.569984 0.83677 0.56948 0.692409294872 - C2 0.836871 0.57037 0.02334 0.692409294872 - C2 0.023722 0.569814 0.83672 0.692409294872 - C2 0.429962 0.430184 0.16327 0.692409294872 - C2 0.430347 0.429912 0.97640 0.692409294872 - C2 0.975946 0.163219 0.43052 0.692409294872 - C2 0.163042 0.976837 0.42973 0.692409294872 - C2 0.163094 0.429662 0.97669 0.692409294872 - C2 0.976236 0.430203 0.163 0.692409294872 - C2 0.429978 0.163203 0.43056 0.692409294872 - C2 0.430275 0.976585 0.42990 0.692409294872 - C2 0.976078 0.430178 0.43045 0.692409294872 - C2 0.163096 0.430024 0.43030 0.692409294872 - C2 0.430112 0.163016 0.97643 0.692409294872 - C2 0.430247 0.976024 0.16318 0.692409294872 - C3 0.29982 0.971615 0.42857 0.0341588782051 - C3 0.970626 0.299953 0.30018 0.0341588782051 - C3 0.428786 0.299926 0.30017 0.0341588782051 - C3 0.299903 0.428483 0.97147 0.0341588782051 - C3 0.299666 0.428844 0.29993 0.0341588782051 - C3 0.970644 0.299906 0.42939 0.0341588782051 - C3 0.429112 0.299661 0.97110 0.0341588782051 - C3 0.299851 0.971008 0.29968 0.0341588782051 - C3 0.299767 0.299475 0.97131 0.0341588782051 - C3 0.97101 0.428978 0.30005 0.0341588782051 - C3 0.429169 0.970912 0.29978 0.0341588782051 - C3 0.299698 0.299764 0.42928 0.0341588782051 - C3 0.029306 0.700049 0.57063 0.0341588782051 - C3 0.700131 0.028932 0.70035 0.0341588782051 - C3 0.700321 0.57111 0.70005 0.0341588782051 - C3 0.570856 0.700243 0.02892 0.0341588782051 - C3 0.5712 0.700028 0.69984 0.0341588782051 - C3 0.700179 0.028352 0.57144 0.0341588782051 - C3 0.700107 0.571457 0.02856 0.0341588782051 - C3 0.029328 0.70004 0.69982 0.0341588782051 - C3 0.70015 0.700508 0.02872 0.0341588782051 - C3 0.570817 0.02905 0.7002 0.0341588782051 - C3 0.028924 0.571002 0.70000 0.0341588782051 - C3 0.700272 0.700216 0.5707 0.0341588782051 - O1 0.613937 0.492317 0.87335 -0.569640705128 - O1 0.492152 0.613822 0.0206 -0.569640705128 - O1 0.873625 0.020109 0.61422 -0.569640705128 - O1 0.020827 0.873315 0.4920 -0.569640705128 - O1 0.61378 0.873494 0.02066 -0.569640705128 - O1 0.492202 0.020866 0.87325 -0.569640705128 - O1 0.873398 0.613903 0.49210 -0.569640705128 - O1 0.020853 0.492246 0.61366 -0.569640705128 - O1 0.613694 0.020561 0.49257 -0.569640705128 - O1 0.492503 0.873086 0.61345 -0.569640705128 - O1 0.873137 0.492907 0.02046 -0.569640705128 - O1 0.020922 0.613731 0.87318 -0.569640705128 - O1 0.507458 0.386317 0.12688 -0.569640705128 - O1 0.386412 0.50739 0.97921 -0.569640705128 - O1 0.978811 0.126777 0.38655 -0.569640705128 - O1 0.126736 0.97978 0.50720 -0.569640705128 - O1 0.126374 0.385808 0.97965 -0.569640705128 - O1 0.979462 0.507647 0.12672 -0.569640705128 - O1 0.386036 0.12656 0.50805 -0.569640705128 - O1 0.507793 0.979634 0.38598 -0.569640705128 - O1 0.978996 0.386216 0.50802 -0.569640705128 - O1 0.126687 0.507544 0.38625 -0.569640705128 - O1 0.50764 0.126662 0.97942 -0.569640705128 - O1 0.386241 0.978913 0.1266 -0.569640705128 - O1 0.386029 0.507632 0.12664 -0.569640705128 - O1 0.507866 0.386077 0.97939 -0.569640705128 - O1 0.126366 0.979858 0.38580 -0.569640705128 - O1 0.979179 0.12664 0.5079 -0.569640705128 - O1 0.386101 0.126452 0.97939 -0.569640705128 - O1 0.507756 0.979114 0.12675 -0.569640705128 - O1 0.126582 0.386094 0.5078 -0.569640705128 - O1 0.979096 0.5077 0.38639 -0.569640705128 - O1 0.386304 0.979437 0.50740 -0.569640705128 - O1 0.507457 0.126882 0.38659 -0.569640705128 - O1 0.126852 0.50713 0.97955 -0.569640705128 - O1 0.979018 0.386304 0.12683 -0.569640705128 - O1 0.492524 0.613647 0.87311 -0.569640705128 - O1 0.613621 0.492521 0.02076 -0.569640705128 - O1 0.02117 0.873189 0.61343 -0.569640705128 - O1 0.873262 0.02016 0.49283 -0.569640705128 - O1 0.873573 0.614239 0.02038 -0.569640705128 - O1 0.020513 0.492364 0.87330 -0.569640705128 - O1 0.613928 0.873424 0.49199 -0.569640705128 - O1 0.492215 0.02033 0.61401 -0.569640705128 - O1 0.020989 0.613713 0.49202 -0.569640705128 - O1 0.873291 0.492436 0.61372 -0.569640705128 - O1 0.492277 0.873288 0.02055 -0.569640705128 - O1 0.613735 0.020997 0.87337 -0.569640705128 diff --git a/test/henry_checkpoint_test.jl b/test/henry_checkpoint_test.jl index 6aa35e8c4..f5f7ff215 100644 --- a/test/henry_checkpoint_test.jl +++ b/test/henry_checkpoint_test.jl @@ -4,7 +4,7 @@ using FileIO using Test @testset "Henry Checkpoint Tests" begin - framework = Framework("FIQCEN_clean_min_charges.cif") + framework = Framework("FIQCEN_clean.cif") strip_numbers_from_atom_labels!(framework) co2 = Molecule("CO2") ljff = LJForceField("UFF.csv") From 1b1663740ba98cece2046467e6a45a9a768ec0a2 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 15 Oct 2019 17:57:57 -0700 Subject: [PATCH 096/105] Add default_bondingrules to docs --- docs/src/manual/boxes_crystals_grids.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/manual/boxes_crystals_grids.md b/docs/src/manual/boxes_crystals_grids.md index 481501d2d..11f3ac0a8 100644 --- a/docs/src/manual/boxes_crystals_grids.md +++ b/docs/src/manual/boxes_crystals_grids.md @@ -209,6 +209,7 @@ write_cube(grid, "CH4_in_SBMOF1.cube") infer_bonds! remove_bonds! compare_bonds_in_framework + default_bondingrules ``` ## Grids From c9c0ddbab08dda0f81dfde5c2a3d944d887e5f53 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 29 Oct 2019 11:15:06 -0700 Subject: [PATCH 097/105] Add option to skip filename for bond information when writing to a file --- src/Crystal.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 704f54326..2d304a5b4 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1180,12 +1180,13 @@ end """ write_bond_information(framework, filename) + write_bond_information(framework) Writes the bond information from a framework to the selected filename. # Arguments -`framework::Framework`: The framework to have its bonds written to a vtk file --`filename::AbstractString`: The filename the bond information will be saved to +-`filename::AbstractString`: The filename the bond information will be saved to. If left out, will default to framework name. """ function write_bond_information(framework::Framework, filename::AbstractString) if ne(framework.bonds) == 0 @@ -1210,6 +1211,8 @@ function write_bond_information(framework::Framework, filename::AbstractString) @printf("Saving bond information for framework %s to %s.\n", framework.name, joinpath(pwd(), filename)) end +write_bond_information(framework::Framework) = write_bond_information(framework, framework.name * "_bonds.vtk") + """ new_framework = assign_charges(framework, charges, net_charge_tol=1e-5) From 1ed40f57c2ba8d5eb26908e355568a092f33d266 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 29 Oct 2019 11:46:46 -0700 Subject: [PATCH 098/105] Made periodic argument a requirement for infer_bonds --- src/Crystal.jl | 14 +++++++------- test/crystal_test.jl | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 2d304a5b4..20eb83505 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1003,8 +1003,8 @@ function remove_bonds!(framework::Framework) end """ - infer_bonds!(framework, bonding_rules=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)], - include_bonds_across_periodic_boundaries=true) + infer_bonds!(framework, include_bonds_across_periodic_boundaries, + bonding_rules=[BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]) Populate the bonds in the framework object based on the bonding rules. If a pair doesn't have a suitable rule then they will not be considered bonded. @@ -1017,15 +1017,15 @@ The bonding rules are hierarchical, i.e. the first bonding rule takes precedence # Arguments -`framework::Framework`: The framework that bonds will be added to +-`include_bonds_across_periodic_boundaries::Bool`: Whether to check across the + periodic boundary when calculating bonds -`bonding_rules::Array{BondingRule, 1}`: The array of bonding rules that will be used to fill the bonding information. They are applied in the order that they appear. --`include_bonds_across_periodic_boundaries::Bool`: Whether to check across the - periodic boundary when calculating bonds """ -function infer_bonds!(framework::Framework, bonding_rules::Array{BondingRule, 1}= - [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]; - include_bonds_across_periodic_boundaries::Bool=true) +function infer_bonds!(framework::Framework, include_bonds_across_periodic_boundaries::Bool, + bonding_rules::Array{BondingRule, 1}= + [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)]) @assert ne(framework.bonds) == 0 @sprintf("The framework %s already has bonds. Remove them with the `remove_bonds!` function before inferring new ones.", framework.name) # loop over every atom diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 6ab289fb7..9e6a35a89 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -134,7 +134,7 @@ using Random bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:Ca, :O, 0.4, 2.5), BondingRule(:*, :*, 0.4, 1.9)] - infer_bonds!(sbmof_bonds, bonding_rules) + infer_bonds!(sbmof_bonds, true, bonding_rules) @test_throws AssertionError replicate(sbmof_bonds, (2, 2, 2)) # write out and compare to inferred bonds write_cif(sbmof_bonds, joinpath(pwd(), "data", "crystals", "SBMOF-1_inferred_bonds.cif")) @@ -150,7 +150,7 @@ using Random # reverse the order of the atoms and bond info should still be the same sbmof_bonds_copy.atoms.xf .= reverse(sbmof_bonds_copy.atoms.xf; dims=2) sbmof_bonds_copy.atoms.species .= reverse(sbmof_bonds_copy.atoms.species) - infer_bonds!(sbmof_bonds_copy, bonding_rules) + infer_bonds!(sbmof_bonds_copy, true, bonding_rules) @test compare_bonds_in_framework(sbmof_bonds, sbmof_bonds_copy) remove_bonds!(sbmof_bonds) @test ne(sbmof_bonds.bonds) == 0 @@ -174,7 +174,7 @@ using Random inferred_bonds = Framework("KAXQIL_clean.cif"; convert_to_p1=false) strip_numbers_from_atom_labels!(inferred_bonds) # Using same bonding rules as above - infer_bonds!(inferred_bonds, bonding_rules) + infer_bonds!(inferred_bonds, true, bonding_rules) @test compare_bonds_in_framework(read_bonds, inferred_bonds; atol=1e-6) repfactors = replication_factors(sbmof.box, 14.0) @@ -220,10 +220,10 @@ using Random BondingRule(:c, :d, 4.5, 5.3)] @test is_bonded(f1, 1, 2, [BondingRule(:a, :b, 1.0, 5.5)]; include_bonds_across_periodic_boundaries=false) @test ! is_bonded(f2, 1, 2, [BondingRule(:c, :d, 1.0, 4.5)]; include_bonds_across_periodic_boundaries=false) - infer_bonds!(f1, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) - infer_bonds!(f2, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) + infer_bonds!(f1, false, addition_bonding_rules) + infer_bonds!(f2, false, addition_bonding_rules) @test ! compare_bonds_in_framework(f1 + f2, f3) - infer_bonds!(f3, addition_bonding_rules; include_bonds_across_periodic_boundaries=false) + infer_bonds!(f3, false, addition_bonding_rules) @test compare_bonds_in_framework(f1 + f2, f3) @test_throws AssertionError f1 + sbmof # only allow frameworks with same box @test isapprox(f1.box, f3.box) From 96c9b5816fbcc6e5b28462a4a6977b4e15074d44 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 29 Oct 2019 15:10:47 -0700 Subject: [PATCH 099/105] Updated docs --- docs/src/manual/boxes_crystals_grids.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/src/manual/boxes_crystals_grids.md b/docs/src/manual/boxes_crystals_grids.md index 11f3ac0a8..509dafae0 100644 --- a/docs/src/manual/boxes_crystals_grids.md +++ b/docs/src/manual/boxes_crystals_grids.md @@ -86,19 +86,21 @@ f = Framework("SBMOF-1.cif") bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), BondingRule(:*, :*, 0.4, 1.9)] -# infer the bonds for the framework f -infer_bonds!(f, bonding_rules) +# Alternatively, you could get the above bonding rules with the following command +bonding_rules = default_bondingrules() -# redefine bonding_rules to account for edge cases between Ca and O atoms -bonding_rules = [BondingRule(:H, :*, 0.4, 1.2), - BondingRule(:Ca, :O, 0.4, 2.5), - BondingRule(:*, :*, 0.4, 1.9)] +# infer the bonds for the framework f with bonds across periodic boundaries +infer_bonds!(f, true, bonding_rules) + +# redefine bonding_rules to account for edge cases between Ca and O atoms. `pushfirst!` adds the newly +# defined Bondingrule to the front of `bonding_rules` +pushfirst!(BondingRule(:Ca, :O, 0.4, 2.5), bonding_rules) # remove old bonds from framework before inferring bonds with new rules remove_bonds!(f) # re-infer bonds -infer_bonds!(f, bonding_rules) +infer_bonds!(f, true, bonding_rules) # output the bond information to visualize it and double check write_bond_information(f, "SBMOF-1_bonds.vtk") From aebd3fbbbd285085fdf426c59368144a5ba01a2e Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Tue, 29 Oct 2019 15:13:30 -0700 Subject: [PATCH 100/105] Removed non-P1 handling from help wanted part in docs --- docs/src/guides/help_wanted.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/guides/help_wanted.md b/docs/src/guides/help_wanted.md index bc984b09a..e908a56d5 100755 --- a/docs/src/guides/help_wanted.md +++ b/docs/src/guides/help_wanted.md @@ -8,7 +8,7 @@ * consolidate `eikar`, `eikbr`, `eikcr` somehow without slowing down the Ewald sum. * more tests added to `tests/runtests.jl`, `tests/henry_tests.jl`, `tests/gcmc_tests.jl` * geometric-based pore size calculations (largest free and included spheres), surface area, and porosity calculations that take `Framework`'s as input -* handle .cif's without P1 symmetry. i.e. convert any .cif to P1 symmetry +* ~~handle .cif's without P1 symmetry. i.e. convert any .cif to P1 symmetry~~ * extend `gcmc_simulation` to handle mixtures * better default rules for choosing Ewald sum parameters? alpha, kvectors required... * Henry coefficient code prints off Ewald sum params 5 times if run with one core... From d16aed5a5595636197d8f7b59c8bf281d8d397cb Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Thu, 31 Oct 2019 15:48:58 -0700 Subject: [PATCH 101/105] Added a test with reference bonds from hkust1 --- test/crystal_test.jl | 10 ++ test/hkust1_reference_bonds.lgz | 193 ++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 test/hkust1_reference_bonds.lgz diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 9e6a35a89..094df4b87 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -250,6 +250,16 @@ using Random @test rbox.Ω ≈ sbmof1.box.Ω * 2 * 3 * 4 @test all(rbox.c_to_f * sbmof1.box.f_to_c * [1.0, 1.0, 1.0] .≈ [1/2, 1/3, 1/4]) + # write bond information to manually inspect if bonds are in order + hkust1 = Framework("FIQCEN_clean.cif") + strip_numbers_from_atom_labels!(hkust1) + br = default_bondingrules() + pushfirst!(br, BondingRule(:Cu, :O, 0.3, 2.4)) + infer_bonds!(hkust1, true, br) + reference_bonds = loadgraph("hkust1_reference_bonds.lgz") + @test reference_bonds == hkust1.bonds + + #= ## .cssr reader test # test replicate framework sbmof = Framework("SBMOF-1.cif") diff --git a/test/hkust1_reference_bonds.lgz b/test/hkust1_reference_bonds.lgz new file mode 100644 index 000000000..586e8e3a2 --- /dev/null +++ b/test/hkust1_reference_bonds.lgz @@ -0,0 +1,193 @@ +156,192,u,graph,2,Int64,simplegraph +1,117 +1,128 +1,142 +1,151 +2,120 +2,126 +2,143 +2,149 +3,118 +3,127 +3,141 +3,152 +4,119 +4,125 +4,144 +4,150 +5,109 +5,121 +5,134 +5,146 +6,112 +6,123 +6,135 +6,148 +7,110 +7,122 +7,133 +7,145 +8,111 +8,124 +8,136 +8,147 +9,113 +9,131 +9,138 +9,156 +10,116 +10,129 +10,139 +10,154 +11,114 +11,132 +11,137 +11,155 +12,115 +12,130 +12,140 +12,153 +13,108 +14,105 +15,106 +16,107 +17,103 +18,102 +19,101 +20,104 +21,98 +22,99 +23,100 +24,97 +25,96 +26,93 +27,94 +28,95 +29,91 +30,90 +31,89 +32,92 +33,86 +34,87 +35,88 +36,85 +37,76 +37,85 +37,92 +38,75 +38,86 +38,90 +39,73 +39,87 +39,89 +40,74 +40,88 +40,91 +41,82 +41,89 +41,96 +42,81 +42,90 +42,94 +43,83 +43,91 +43,93 +44,84 +44,92 +44,95 +45,77 +45,88 +45,93 +46,78 +46,86 +46,94 +47,80 +47,85 +47,95 +48,79 +48,87 +48,96 +49,64 +49,97 +49,104 +50,63 +50,98 +50,102 +51,61 +51,99 +51,101 +52,62 +52,100 +52,103 +53,70 +53,101 +53,108 +54,69 +54,102 +54,106 +55,71 +55,103 +55,105 +56,72 +56,104 +56,107 +57,65 +57,100 +57,105 +58,66 +58,98 +58,106 +59,68 +59,97 +59,107 +60,67 +60,99 +60,108 +61,109 +61,145 +62,110 +62,146 +63,111 +63,148 +64,112 +64,147 +65,113 +65,155 +66,114 +66,156 +67,115 +67,154 +68,116 +68,153 +69,117 +69,152 +70,118 +70,151 +71,119 +71,149 +72,120 +72,150 +73,121 +73,133 +74,122 +74,134 +75,123 +75,136 +76,124 +76,135 +77,125 +77,143 +78,126 +78,144 +79,127 +79,142 +80,128 +80,141 +81,129 +81,140 +82,130 +82,139 +83,131 +83,137 +84,132 +84,138 From 37fcb6203312f2b0cef97e1d8a8a72e8529593ae Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Wed, 27 Nov 2019 16:31:15 -0800 Subject: [PATCH 102/105] Changed isapprox for charges, atoms and frameworks. now a seperate function --- src/Crystal.jl | 21 ++++++++++++++++++--- src/Matter.jl | 12 ++++++++++-- src/PorousMaterials.jl | 4 ++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 20eb83505..853b58627 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -1365,7 +1365,7 @@ function Base.show(io::IO, framework::Framework) end end -function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checknames::Bool=false) +function has_same_sets_of_atoms_and_charges(f1::Framework, f2::Framework; atol::Float64=1e-6, checknames::Bool=false) names_flag = f1.name == f2.name if checknames && (! names_flag) return false @@ -1377,8 +1377,23 @@ function Base.isapprox(f1::Framework, f2::Framework; atol::Float64=1e-6, checkna if f1.atoms.n_atoms != f2.atoms.n_atoms return false end - charges_flag = isapprox(f1.charges, f2.charges; atol=atol) - atoms_flag = isapprox(f1.atoms, f2.atoms; atol=atol) + charges_flag = has_same_set_of_charges(f1.charges, f2.charges; atol=atol) + atoms_flag = has_same_set_of_atoms(f1.atoms, f2.atoms; atol=atol) + symmetry_flag = is_symmetry_equal(f1.symmetry, f2.symmetry) + return box_flag && charges_flag && atoms_flag && symmetry_flag +end + + +function Base.isapprox(f1::Framework, f2::Framework) + box_flag = isapprox(f1.box, f2.box) + if f1.charges.n_charges != f2.charges.n_charges + return false + end + if f1.atoms.n_atoms != f2.atoms.n_atoms + return false + end + charges_flag = isapprox(f1.charges, f2.charges) + atoms_flag = isapprox(f1.atoms, f2.atoms) symmetry_flag = is_symmetry_equal(f1.symmetry, f2.symmetry) return box_flag && charges_flag && atoms_flag && symmetry_flag end diff --git a/src/Matter.jl b/src/Matter.jl index 0fea9b5b0..f8282e489 100644 --- a/src/Matter.jl +++ b/src/Matter.jl @@ -20,9 +20,13 @@ end # compute n_species automatically from array sizes Atoms(species::Array{Symbol, 1}, xf::Array{Float64, 2}) = Atoms(size(xf, 2), species, xf) -Base.isapprox(a1::Atoms, a2::Atoms; atol::Float64=1e-6) = issetequal( +Base.isapprox(a1::Atoms, a2::Atoms) = (a1.species == a2.species) && isapprox(a1.xf, a2.xf) + +function has_same_set_of_atoms(a1::Atoms, a2::Atoms; atol::Float64=1e-6) + return issetequal( Set([(round.(a1.xf[:, i], digits=Int(abs(log10(atol)))), a1.species[i]) for i in 1:a1.n_atoms]), Set([(round.(a2.xf[:, i], digits=Int(abs(log10(atol)))), a2.species[i]) for i in 1:a2.n_atoms])) +end Base.:+(a1::Atoms, a2::Atoms) = Atoms(a1.n_atoms + a2.n_atoms, [a1.species; a2.species], [a1.xf a2.xf]) @@ -48,8 +52,12 @@ end # compute n_charges automatically from array sizes Charges(q::Array{Float64, 1}, xf::Array{Float64, 2}) = Charges(size(xf, 2), q, xf) -Base.isapprox(c1::Charges, c2::Charges; atol::Float64=1e-6) = issetequal( +Base.isapprox(c1::Charges, c2::Charges) = isapprox(c1.q, c2.q) && isapprox(c1.xf, c2.xf) + +function has_same_set_of_charges(c1::Charges, c2::Charges; atol::Float64=1e-6) + return issetequal( Set([(round.(c1.xf[:, i], digits=Int(abs(log10(atol)))), c1.q[i]) for i in 1:c1.n_charges]), Set([(round.(c2.xf[:, i], digits=Int(abs(log10(atol)))), c2.q[i]) for i in 1:c2.n_charges])) +end Base.:+(c1::Charges, c2::Charges) = Charges(c1.n_charges + c2.n_charges, [c1.q; c2.q], [c1.xf c2.xf]) diff --git a/src/PorousMaterials.jl b/src/PorousMaterials.jl index ce96dd585..994c1228a 100644 --- a/src/PorousMaterials.jl +++ b/src/PorousMaterials.jl @@ -104,7 +104,7 @@ export Box, replicate, UnitCube, write_vtk, inside, # Matter.jl - Atoms, Charges, + Atoms, Charges, has_same_set_of_atoms, has_same_set_of_charges, # NearestImage.jl nearest_image!, nearest_r², nearest_r, @@ -119,7 +119,7 @@ export replicate, read_atomic_masses, charged, write_cif, assign_charges, is_symmetry_equal, apply_symmetry_rules, assert_P1_symmetry, infer_bonds!, remove_bonds!, compare_bonds_in_framework, wrap_atoms_to_unit_cell!, - write_bond_information, is_bonded, default_bondingrules, + write_bond_information, is_bonded, default_bondingrules, has_same_sets_of_atoms_and_charges, # Molecules.jl Molecule, n_atoms, set_fractional_coords!, translate_by!, outside_box, From 597d0286436f382107084b92803b230847870712 Mon Sep 17 00:00:00 2001 From: Arni Sturluson Date: Wed, 27 Nov 2019 16:31:36 -0800 Subject: [PATCH 103/105] Modified tests to deal with new approx funcs --- test/crystal_test.jl | 22 +++++++++++----------- test/matter_test.jl | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/crystal_test.jl b/test/crystal_test.jl index 094df4b87..a10774dc9 100644 --- a/test/crystal_test.jl +++ b/test/crystal_test.jl @@ -15,19 +15,19 @@ using Random @test framework.name == "test_structure2.cif" @test isapprox(framework.box, Box(10.0, 20.0, 30.0, 90*π/180, 45*π/180, 120*π/180)) @test framework.atoms.n_atoms == 2 - @test isapprox(framework.atoms, Atoms([:Ca, :O], [0.2 0.6; 0.5 0.3; 0.7 0.1])) - @test isapprox(framework.charges, Charges([1.0, -1.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) + @test has_same_set_of_atoms(framework.atoms, Atoms([:Ca, :O], [0.2 0.6; 0.5 0.3; 0.7 0.1])) + @test has_same_set_of_charges(framework.charges, Charges([1.0, -1.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) new_frame = assign_charges(framework, Dict(:Ca => -2.0, :O => 2.0)) - @test isapprox(new_frame.charges, Charges([-2.0, 2.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) + @test has_same_set_of_charges(new_frame.charges, Charges([-2.0, 2.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) new_frame = assign_charges(framework, [4.0, -4.0]) - @test isapprox(new_frame.charges, Charges([4.0, -4.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) + @test has_same_set_of_charges(new_frame.charges, Charges([4.0, -4.0], [0.2 0.6; 0.5 0.3; 0.7 0.1])) @test charged(framework) @test chemical_formula(framework) == Dict(:Ca => 1, :O => 1) @test molecular_weight(framework) ≈ 15.9994 + 40.078 # same as test_structure.cif but with overlapping atoms. framework2 = Framework("test_structure2B.cif", remove_overlap=true, check_charge_neutrality=false) strip_numbers_from_atom_labels!(framework2) - @test isapprox(framework.atoms, framework2.atoms) && isapprox(framework.charges, framework2.charges) + @test has_same_set_of_atoms(framework.atoms, framework2.atoms) && has_same_set_of_charges(framework.charges, framework2.charges) # test .cif writer; write, read in, assert equal # more write_cif tests below with symmetry tests @@ -37,8 +37,8 @@ using Random write_cif(framework, joinpath("data", "crystals", "rewritten_test_structure2_cartn.cif"); fractional=false) framework_rewritten_cartn = Framework("rewritten_test_structure2_cartn.cif") strip_numbers_from_atom_labels!(framework_rewritten_cartn) - @test isapprox(framework, framework_rewritten) - @test isapprox(framework, framework_rewritten_cartn) + @test has_same_sets_of_atoms_and_charges(framework, framework_rewritten) + @test has_same_sets_of_atoms_and_charges(framework, framework_rewritten_cartn) # test .cif reader for non-P1 symmetry # no atoms should overlap @@ -61,11 +61,11 @@ using Random P1_framework.atoms.xf .= mod.(P1_framework.atoms.xf, 1.0) P1_framework.charges.xf .= mod.(P1_framework.charges.xf, 1.0) - @test isapprox(non_P1_framework, P1_framework; atol=1e-2) + @test has_same_sets_of_atoms_and_charges(non_P1_framework, P1_framework; atol=1e-2) # test that fractional and cartesian produce same results - @test isapprox(non_P1_framework, non_P1_cartesian; atol=1e-2) + @test has_same_sets_of_atoms_and_charges(non_P1_framework, non_P1_cartesian; atol=1e-2) # test that cartesian and P1 produce same results - @test isapprox(non_P1_cartesian, P1_framework; atol=1e-2) + @test has_same_sets_of_atoms_and_charges(non_P1_cartesian, P1_framework; atol=1e-2) # test that incorrect file formats throw proper errors @test_throws ErrorException Framework("non_P1_no_symmetry.cif") # test that a file with no atoms throws error @@ -121,7 +121,7 @@ using Random # test .cssr reader too; test_structure2.{cif,cssr} designed to be the same. framework_from_cssr = Framework("test_structure2.cssr") strip_numbers_from_atom_labels!(framework_from_cssr) - @test isapprox(framework_from_cssr, framework, checknames=false) + @test has_same_sets_of_atoms_and_charges(framework_from_cssr, framework, checknames=false) # test replicate framework sbmof = Framework("SBMOF-1.cif") diff --git a/test/matter_test.jl b/test/matter_test.jl index bf87c5b9f..4f11544bc 100644 --- a/test/matter_test.jl +++ b/test/matter_test.jl @@ -26,7 +26,7 @@ using Test a1_mismatch = Atoms([:b, :a], [1.0 4.0; 2.0 5.0; 3.0 6.0]) - @test ! isapprox(a1, a1_mismatch) + @test ! has_same_set_of_atoms(a1, a1_mismatch) # testing addition for charges type c1 = Charges([0.1, 0.2], [1.0 4.0; @@ -49,7 +49,7 @@ using Test c1_mismatch = Charges([0.2, 0.1], [1.0 4.0; 2.0 5.0; 3.0 6.0]) - @test ! isapprox(c1, c1_mismatch) + @test ! has_same_set_of_charges(c1, c1_mismatch) end end From a22a047bef7eb2c0c8f25e05ef13fae43c6e1200 Mon Sep 17 00:00:00 2001 From: SimonEnsemble Date: Thu, 28 Nov 2019 11:05:32 -0800 Subject: [PATCH 104/105] clarify errors and doc strings; fixed bug where the framework was returned before undergoing wrap_atoms_to_unit_cell and returning framework. put wrap_to_unit_cell after p1 conversion done. --- src/Crystal.jl | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 853b58627..384b8a03a 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -77,11 +77,11 @@ function it is assumed it is in P1 symmetry. - `check_atom_and_charge_overlap::Bool`: throw an error if overlapping atoms are detected. - `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. - `convert_to_p1::Bool`: If the structure is not in P1 it will be converted to - P1 symmetry using the symmetry rules. The space groups name will not be - used when converting from the lower level symmetry to P1. + P1 symmetry using the symmetry rules from the `_symmetry_equiv_pos_as_xyz` list in the .cif file. + (We do not use the space groups name to look up symmetry rules). - `read_bonds_from_file::Bool`: Whether or not to read bonding information from - cif file. If false, the bonds can be inferred later -- `wrap_to_unit_cell::Bool`: Whether the atoms and charges will be wrapped to the unit cell after being read in + cif file. If false, the bonds can be inferred later. note that, if the crystal is not in P1 symmetry, we cannot *both* read bonds and convert to P1 symmetry. +- `wrap_to_unit_cell::Bool`: if true, enforce that fractional coords of atoms/charges are in [0,1]³ by mod(x, 1) # Returns - `framework::Framework`: A framework containing the crystal structure information @@ -357,7 +357,8 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, # Structure must either be in P1 symmetry or have replication information if !p1_symmetry && !symmetry_info - error("If structure is not in P1 symmetry it must have replication information") + error(@sprintf("%s is not in P1 symmetry and the .cif does not have a _symmetry_equiv_pos_as_xyz column + for us to apply symmetry operations to convert into P1 symmetry.", filename)) end a = data["a"] @@ -433,15 +434,18 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, end end - if wrap_to_unit_cell - wrap_atoms_to_unit_cell!(framework) - end - if convert_to_p1 && ! p1_symmetry && ! read_bonds_from_file - return apply_symmetry_rules(framework; remove_overlap=remove_overlap, + @warn @sprintf("Framework %s has %s space group. We are converting it to P1 symmetry for use in molecular simulations. + To afrain from this, pass `convert_to_p1=false` to the `Framework` constructor.\n", + framework.name, framework.space_group) + framework = apply_symmetry_rules(framework; remove_overlap=remove_overlap, check_charge_neutrality=check_charge_neutrality, check_atom_and_charge_overlap=check_atom_and_charge_overlap) end + + if wrap_to_unit_cell + wrap_atoms_to_unit_cell!(framework) + end if remove_overlap return remove_overlapping_atoms_and_charges(framework) From 2c0e629020b552cd419a11d27c473eae2adad31f Mon Sep 17 00:00:00 2001 From: SimonEnsemble Date: Thu, 28 Nov 2019 11:14:44 -0800 Subject: [PATCH 105/105] add wrap atoms to p1 symmetry converter; return right away to avoid calling overlap code twice --- src/Crystal.jl | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Crystal.jl b/src/Crystal.jl index 384b8a03a..2d961421e 100644 --- a/src/Crystal.jl +++ b/src/Crystal.jl @@ -438,7 +438,7 @@ function Framework(filename::AbstractString; check_charge_neutrality::Bool=true, @warn @sprintf("Framework %s has %s space group. We are converting it to P1 symmetry for use in molecular simulations. To afrain from this, pass `convert_to_p1=false` to the `Framework` constructor.\n", framework.name, framework.space_group) - framework = apply_symmetry_rules(framework; remove_overlap=remove_overlap, + return apply_symmetry_rules(framework; remove_overlap=remove_overlap, check_charge_neutrality=check_charge_neutrality, check_atom_and_charge_overlap=check_atom_and_charge_overlap) end @@ -848,7 +848,8 @@ end check_charge_neutrality=true, net_charge_tol=0.001, check_atom_and_charge_overlap=true, - remove_overlap=false) + remove_overlap=false, + wrap_to_unit_cell=true) Convert a framework to P1 symmetry based on internal symmetry rules. This will return the new framework. @@ -859,6 +860,7 @@ return the new framework. - `net_charge_tol::Float64`: when checking for charge neutrality, throw an error if the absolute value of the net charge is larger than this value. - `check_atom_and_charge_overlap::Bool`: throw an error if overlapping atoms are detected. - `remove_overlap::Bool`: remove identical atoms automatically. Identical atoms are the same element atoms which overlap. +- `wrap_to_unit_cell::Bool`: if true, enforce that fractional coords of atoms/charges are in [0,1]³ by mod(x, 1) # Returns - `P1_framework::Framework`: The framework after it has been converted to P1 @@ -866,7 +868,7 @@ return the new framework. """ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Bool=true, net_charge_tol::Float64=0.001, check_atom_and_charge_overlap::Bool=true, - remove_overlap::Bool=false) + remove_overlap::Bool=false, wrap_to_unit_cell::Bool=true) if framework.is_p1 return framework end @@ -909,6 +911,10 @@ function apply_symmetry_rules(framework::Framework; check_charge_neutrality::Boo new_framework.name, total_charge(new_framework))) end end + + if wrap_to_unit_cell + wrap_atoms_to_unit_cell!(framework) + end if remove_overlap return remove_overlapping_atoms_and_charges(new_framework)