From 6903f6ec00b38c80d1b5173df3047a7f7740525c Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 15 Mar 2018 12:04:15 -0400 Subject: [PATCH 1/3] many improvements --- README.md | 106 +++++++------------ src/PeriodicTable.jl | 242 +++++++++++++++++++++++++------------------ test/runtests.jl | 17 ++- 3 files changed, 185 insertions(+), 180 deletions(-) diff --git a/README.md b/README.md index 0a80036..eaf5fb1 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,36 @@ julia> Pkg.update("PeriodicTable") ``` ## How it works? -PeriodicTable.jl includes a `elements.json` file which acts like a database for this small library. -Working with it is very simple, indeed its just 2 steps before you see the actual data. +PeriodicTable.jl provides a Julia interface to a small database of element +properties for all of the elements in the periodic table. In particular +```jl +using PeriodicTable +``` +exports a global variable called `elements`, which is a collection of +`Element` data structures. You can look up elements by name (case-insensitive) +via `elements["oxygen"]`, by symbol via `elements[:O]`, or by number via +`elements[8]`, for example. + +Each element has fields `name`, `appearance`, `atomic_mass`, `boil`, `category`, `color`, `density`, `discovered_by`, `melt`, `molar_heat`, `named_by`, `number`, `period`, `phase`, `source`, `spectral_img`, `summary`, `symbol`, `xpos`, `ypos`, `shells`. + +This data is pretty-printed when you look up an element in the Julia REPL. +For example: +```jl +julia> elements["oxygen"] +Oxygen (O), number 8: + category: diatomic nonmetal + atomic mass: 15.999 u + density: 1.429 g/cm³ + melting point: 54.36 K + boiling point: 90.188 K + phase: Gas + shells: [2, 6] + summary: Oxygen is a chemical element with symbol O and atomic number 8. It is a member of the chalcogen group on the periodic table and is a highly reactive nonmetal and oxidizing agent that readily forms compounds (notably oxides) with most elements. By mass, oxygen is the third-most abundant element in the universe, after hydrogen and helium. + discovered by: Carl Wilhelm Scheele + named by: Antoine Lavoisier + source: https://en.wikipedia.org/wiki/Oxygen + spectral image: https://en.wikipedia.org/wiki/File:Oxygen_spectre.jpg +``` ### View the Periodic Table! This awesome view was added by [Jacob Wikmark](https://github.com/lancebeet) via [#4](https://github.com/rahulkp220/PeriodicTable.jl/pull/4) @@ -24,78 +52,18 @@ This awesome view was added by [Jacob Wikmark](https://github.com/lancebeet) via # Initialise the object julia> p = PeriodicTable.PT() -H He -Li Be B C N O F Ne -Na Mg Al Si P S Cl Ar -K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr -Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe -Cs Ba Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn -Fr Ra Rf Db Sg Bh Hs Mt Ds Rg Cn Nh Fl Mc Lv Ts Og +H He +Li Be B C N O F Ne +Na Mg Al Si P S Cl Ar +K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr +Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe +Cs Ba Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn +Fr Ra Rf Db Sg Bh Hs Mt Ds Rg Cn Nh Fl Mc Lv Ts Og Uue La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr ``` -### Getting the element and their data -```julia -julia> ele = PeriodicTable.get_element(p, 8) - -# to get the complete data -julia> ele.data -Dict{String,Any} with 21 entries: - "number" => 8 - "appearance" => "" - "name" => "Oxygen" - "boil" => 90.188 - "atomic_mass" => 15.999 - "period" => 2 - "melt" => 54.36 - "shells" => Any[2, 6] - "summary" => "Oxygen is a chemical element with symbol O and atomic number 8. It is a member of the chalcogen group on the periodic … - "molar_heat" => "" - "named_by" => "Antoine Lavoisier" - "xpos" => 16 - "symbol" => "O" - "discovered_by" => "Carl Wilhelm Scheele" - "ypos" => 2 - "category" => "diatomic nonmetal" - "density" => 1.429 - "source" => "https://en.wikipedia.org/wiki/Oxygen" - "color" => "" - "phase" => "Gas" - "spectral_img" => "https://en.wikipedia.org/wiki/File:Oxygen_spectre.jpg" - -# exploring these properties via fieldnames -julia> println("$(ele.name) with mass $(ele.atomic_mass)") -Oxygen with mass 15.999 - -julia> fieldnames(ele) -22-element Array{Symbol,1}: - :data - :name - :appearance - :atomic_mass - :boil - :category - :color - :density - :discovered_by - :melt - :molar_heat - :named_by - :number - :period - :phase - :source - :spectral_img - :summary - :symbol - :xpos - :ypos - :shells - -``` - ## Data by * [Periodic-Table-JSON](https://github.com/Bowserinator/Periodic-Table-JSON) diff --git a/src/PeriodicTable.jl b/src/PeriodicTable.jl index 296959d..dc7c898 100644 --- a/src/PeriodicTable.jl +++ b/src/PeriodicTable.jl @@ -1,125 +1,165 @@ +__precompile__(true) + +""" +The PeriodicTable module exports an `elements` variable that stores +data (of type `Element`) on every element in the periodic table. + +The data can be looked up by atomic number via `elements[n]`, by name +(case-insensitive) via e.g. `elements["oxygen"]`, or by symbol via +e.g. `elements[:O]`. +""" module PeriodicTable +export Element, elements -# Import modules using JSON -# constants -const DATA_FILE = "elements.json" -const PACKAGE_NAME = "PeriodicTable" - """ Element composite type """ -type Element - data - name - appearance - atomic_mass - boil - category - color - density - discovered_by - melt - molar_heat - named_by - number - period - phase - source - spectral_img - summary - symbol - xpos - ypos - shells - - # inner constructor - Element(data, - name = get(data, "name", nothing), - appearance = get(data, "appearance", nothing), - atomic_mass = get(data, "atomic_mass", nothing), - boil = get(data, "boil", nothing), - category = get(data, "category", nothing), - color = get(data, "color", nothing), - density = get(data, "density", nothing), - discovered_by = get(data, "discovered_by", nothing), - melt = get(data, "melt", nothing), - molar_heat = get(data, "molar_heat", nothing), - named_by = get(data, "named_by", nothing), - number = get(data, "number", nothing), - period = get(data, "period", nothing), - phase = get(data, "phase", nothing), - source = get(data, "source", nothing), - spectral_img = get(data, "spectral_img", nothing), - summary = get(data, "summary", nothing), - symbol = get(data, "symbol", nothing), - xpos = get(data, "xpos", nothing), - ypos = get(data, "ypos", nothing), - shells = get(data, "shells", nothing)) = new(data,name,appearance,atomic_mass, - boil,category,color,density, - discovered_by,melt,molar_heat, - named_by,number,period,phase,source, - spectral_img,summary,symbol, - xpos,ypos,shells) +mutable struct Element + name::String + appearance::String + atomic_mass::Float64 + boil::Float64 + category::String + color::String + density::Float64 + discovered_by::String + melt::Float64 + molar_heat::Float64 + named_by::String + number::Int + period::Int + phase::String + source::String # url + spectral_img::String # url + summary::String + symbol::String + xpos::Int + ypos::Int + shells::Vector{Int} end +# like get(data, name, default), but treats +# non-numeric data as missing +function getnum(data, name, default::Number) + v = get(data, name, default) + return v isa Number ? v : default +end -""" -PT composite type -""" -type PT - data - - # Helper method, something that I am happy it worked! - function load_data() - output = [] - file_path = joinpath(Pkg.dir(PACKAGE_NAME),"src","data", DATA_FILE) - - # open the file, convert data, append to Array - open(file_path, "r") do file - data_json = JSON.parse(readstring(file)) - for d in data_json - push!(output, Element(d)) - end - end - - output - end - - # inner constructor - function PT() - data = load_data() - new(data) +# constructor from JSON-generated Dict +Element(eldata::Dict) = Element( + get(eldata, "name", ""), + get(eldata, "appearance", ""), + getnum(eldata, "atomic_mass", NaN), + getnum(eldata, "boil", NaN), + get(eldata, "category", ""), + get(eldata, "color", ""), + getnum(eldata, "density", NaN), + get(eldata, "discovered_by", ""), + getnum(eldata, "melt", NaN), + getnum(eldata, "molar_heat", NaN), + get(eldata, "named_by", ""), + getnum(eldata, "number", -1), + getnum(eldata, "period", -1), + get(eldata, "phase", ""), + get(eldata, "source", ""), + get(eldata, "spectral_img", ""), + get(eldata, "summary", ""), + get(eldata, "symbol", ""), + getnum(eldata, "xpos", -1), + getnum(eldata, "ypos", -1), + Vector{Int}(get(eldata, "shells", Int[]))) + +Base.show(io::IO, el::Element) = print(io, "Element(", el.name, ')') + +ispresent(s) = !isempty(s) +ispresent(x::Float64) = !isnan(x) +ispresent(n::Int) = n ≥ 0 +function printpresent(io::IO, name, val, suffix=""; pad=16) + if ispresent(val) + println(io, lpad(name, pad), ": ", val, suffix) end end +function Base.show(io::IO, ::MIME"text/plain", el::Element) + println(io, el.name, " (", el.symbol, "), number ", el.number, ':') + printpresent(io, "category", el.category) + printpresent(io, "atomic mass", el.atomic_mass, " u") + printpresent(io, "density", el.density, " g/cm³") + printpresent(io, "molar heat", el.molar_heat, " J/mol⋅K") + printpresent(io, "melting point", el.melt, " K") + printpresent(io, "boiling point", el.boil, " K") + printpresent(io, "phase", el.phase) + printpresent(io, "shells", el.shells) + printpresent(io, "appearance", el.appearance) + printpresent(io, "color", el.color) + printpresent(io, "summary", el.summary) + printpresent(io, "discovered by", el.discovered_by) + printpresent(io, "named by", el.named_by) + printpresent(io, "source", el.source) + printpresent(io, "spectral image", el.spectral_img) +end """ -Function to get element by atomic number +Elements composite type """ -function get_element(pt::PT, atomic_number::Int64) - pt.data[atomic_number] +struct Elements + data::Vector{Element} + bynumber::Dict{Int,Element} + byname::Dict{String,Element} + bysymbol::Dict{Symbol,Element} + Elements(data::Vector{Element}) = new( + sort!(data, by=d->d.number), + Dict{Int,Element}(d.number=>d for d in data), + Dict{String,Element}(lowercase(d.name)=>d for d in data), + Dict{Symbol,Element}(Symbol(d.symbol)=>d for d in data)) end - +Base.getindex(e::Elements, i::Integer) = e.bynumber[i] +Base.getindex(e::Elements, i::AbstractString) = e.byname[lowercase(i)] +Base.getindex(e::Elements, i::Symbol) = e.bysymbol[i] +Base.haskey(e::Elements, i::Integer) = haskey(e.bynumber, i) +Base.haskey(e::Elements, i::AbstractString) = haskey(e.byname, lowercase(i)) +Base.haskey(e::Elements, i::Symbol) = haskey(e.bysymbol, i) +Base.get(e::Elements, i::Integer, default) = get(e.bynumber, i, default) +Base.get(e::Elements, i::AbstractString, default) = get(e.byname, lowercase(i), default) +Base.get(e::Elements, i::Symbol, default) = get(e.bysymbol, i, default) -""" -Show table -""" -function Base.show(io::IO, ::MIME"text/plain", p::PT) #We can make this static if data is loaded in compile time - table_length = length(p.data) - shape = (10,18) - - array_table = fill(" ", shape) - for i in 1:table_length - el = p.data[i].data - array_table[el["ypos"], el["xpos"]] = rpad(el["symbol"], 3) +# support iterating over elements +Base.eltype(e::Elements) = Element +Base.length(e::Elements) = length(e.data) +Base.start(e::Elements) = start(e.data) +Base.next(e::Elements, i) = next(e.data, i) +Base.done(e::Elements, i) = done(e.data, i) + +# compact one-line printing +Base.show(io::IO, e::Elements) = print(io, "Elements(…", length(e), " elements…)") + +# pretty-printing as a periodic table +function Base.show(io::IO, ::MIME"text/plain", e::Elements) + println(io, e, ':') + table = fill(" ", 10,18) + for el in e + table[el.ypos, el.xpos] = rpad(el.symbol, 3) + end + for i = 1:size(table,1) + for j = 1:size(table, 2) + print(io, table[i,j]) + end + println(io) end - print(join([join(array_table[i,:]) for i in 1:shape[1]], '\n')) end +# load from JSON data +function Elements(filename = joinpath(dirname(@__FILE__), "data", "elements.json")) + data_json = open(JSON.parse, filename) + data = sizehint!(Element[], length(data_json)) + for d in data_json + push!(data, Element(d)) + end + return Elements(data) +end -# exports -export Element, PT, get_element +const elements = Elements() end diff --git a/test/runtests.jl b/test/runtests.jl index 39742d9..c3d0ae4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,15 +1,12 @@ using PeriodicTable using Base.Test -# test PT -p = PeriodicTable.PT() -@test typeof(p.data) == Array{Any,1} -@test typeof(p.data[1]) == PeriodicTable.Element -@test length(p.data) == 119 +@test eltype(elements) = Element +@test length(elements) == 119 # test get_element -pget = PeriodicTable.get_element(p, 8) -@test pget.name == "Oxygen" -@test pget.symbol == "O" -@test length(fieldnames(pget)) == 22 -@test length(fieldnames(pget.data)) == 8 +O = elements[8] +@test O === elements["oxygen"] == elements[:O] +@test O.name == "Oxygen" +@test O.symbol == "O" +@test nfields(O) == 21 From 7fb86f4babb2b784747ef74f7e11afa85e047a4d Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 15 Mar 2018 12:07:14 -0400 Subject: [PATCH 2/3] sync README --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index eaf5fb1..2a43a2b 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,8 @@ Oxygen (O), number 8: ### View the Periodic Table! This awesome view was added by [Jacob Wikmark](https://github.com/lancebeet) via [#4](https://github.com/rahulkp220/PeriodicTable.jl/pull/4) ```julia - -# Initialise the object -julia> p = PeriodicTable.PT() +julia> elements +Elements(…119 elements…): H He Li Be B C N O F Ne Na Mg Al Si P S Cl Ar @@ -61,7 +60,7 @@ Cs Ba Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn Fr Ra Rf Db Sg Bh Hs Mt Ds Rg Cn Nh Fl Mc Lv Ts Og Uue La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu - Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr + Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr ``` ## Data by From 3902e841b294ef6f0d2e70a5b464ffa62bb9d32a Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Thu, 15 Mar 2018 12:08:40 -0400 Subject: [PATCH 3/3] whoops --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index c3d0ae4..38bbbc0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using PeriodicTable using Base.Test -@test eltype(elements) = Element +@test eltype(elements) == Element @test length(elements) == 119 # test get_element