diff --git a/matinfio/cif/cif.ts b/matinfio/cif/cif.ts index b15e9c3..5e10fea 100644 --- a/matinfio/cif/cif.ts +++ b/matinfio/cif/cif.ts @@ -148,7 +148,7 @@ namespace $ { atom.overlays.label = atom.label if( !atom.symbol ) atom.symbol = atom.label.replace( /[0-9]/g, '' ) } - if( !( $optimade_cifplayer_matinfio_chemical_elements.JmolColors as any )[ atom.symbol ] + if( ! $optimade_cifplayer_matinfio_chemical_elements.JmolColors[ atom.symbol ] && atom.symbol && atom.symbol.length > 1 ) { diff --git a/matinfio/matinfio.web.ts b/matinfio/matinfio.web.ts index 4f27d12..413c48f 100644 --- a/matinfio/matinfio.web.ts +++ b/matinfio/matinfio.web.ts @@ -1,6 +1,9 @@ namespace $ { - export const $optimade_cifplayer_matinfio_chemical_elements = { + export const $optimade_cifplayer_matinfio_chemical_elements: { + JmolColors: Record< string, string >, + AseRadii: Record< string, number >, + } = { JmolColors: { "D": "#FFFFC0", "H": "#FFFFFF", "He": "#D9FFFF", "Li": "#CC80FF", "Be": "#C2FF00", "B": "#FFB5B5", "C": "#909090", "N": "#3050F8", "O": "#FF0D0D", "F": "#90E050", "Ne": "#B3E3F5", "Na": "#AB5CF2", "Mg": "#8AFF00", "Al": "#BFA6A6", "Si": "#F0C8A0", "P": "#FF8000", "S": "#FFFF30", "Cl": "#1FF01F", "Ar": "#80D1E3", "K": "#8F40D4", "Ca": "#3DFF00", "Sc": "#E6E6E6", "Ti": "#BFC2C7", "V": "#A6A6AB", "Cr": "#8A99C7", "Mn": "#9C7AC7", "Fe": "#E06633", "Co": "#F090A0", "Ni": "#50D050", "Cu": "#C88033", "Zn": "#7D80B0", "Ga": "#C28F8F", "Ge": "#668F8F", "As": "#BD80E3", "Se": "#FFA100", "Br": "#A62929", "Kr": "#5CB8D1", "Rb": "#702EB0", "Sr": "#00FF00", "Y": "#94FFFF", "Zr": "#94E0E0", "Nb": "#73C2C9", "Mo": "#54B5B5", "Tc": "#3B9E9E", "Ru": "#248F8F", "Rh": "#0A7D8C", "Pd": "#006985", "Ag": "#C0C0C0", "Cd": "#FFD98F", "In": "#A67573", "Sn": "#668080", "Sb": "#9E63B5", "Te": "#D47A00", "I": "#940094", "Xe": "#429EB0", "Cs": "#57178F", "Ba": "#00C900", "La": "#70D4FF", "Ce": "#FFFFC7", "Pr": "#D9FFC7", "Nd": "#C7FFC7", "Pm": "#A3FFC7", "Sm": "#8FFFC7", "Eu": "#61FFC7", "Gd": "#45FFC7", "Tb": "#30FFC7", "Dy": "#1FFFC7", "Ho": "#00FF9C", "Er": "#00E675", "Tm": "#00D452", "Yb": "#00BF38", "Lu": "#00AB24", "Hf": "#4DC2FF", "Ta": "#4DA6FF", "W": "#2194D6", "Re": "#267DAB", "Os": "#266696", "Ir": "#175487", "Pt": "#D0D0E0", "Au": "#FFD123", "Hg": "#B8B8D0", "Tl": "#A6544D", "Pb": "#575961", "Bi": "#9E4FB5", "Po": "#AB5C00", "At": "#754F45", "Rn": "#428296", "Fr": "#420066", "Ra": "#007D00", "Ac": "#70ABFA", "Th": "#00BAFF", "Pa": "#00A1FF", "U": "#008FFF", "Np": "#0080FF", "Pu": "#006BFF", "Am": "#545CF2", "Cm": "#785CE3", "Bk": "#8A4FE3", "Cf": "#A136D4", "Es": "#B31FD4", "Fm": "#B31FBA", "Md": "#B30DA6", "No": "#BD0D87", "Lr": "#C70066", "Rf": "#CC0059", "Db": "#D1004F", "Sg": "#D90045", "Bh": "#E00038", "Hs": "#E6002E", "Mt": "#EB0026" }, // NB starting from Bk the radii data are incorrect @@ -30,7 +33,7 @@ namespace $ { x: number, y: number, z: number, - }, + } | null, x: number, y: number, z: number, @@ -60,19 +63,7 @@ namespace $ { symlabel?: string, }, overlayed: Record< string, string >, - atoms: { - fract: { - x: number, - y: number, - z: number, - } | null, - x: number, - y: number, - z: number, - c: string, //color - r: number, //radius - overlays: Record< string, string | number >, - }[], + atoms: $optimade_cifplayer_matinfio_internal_obj_atom[] sg_name: string, ng_name: number, info: string, @@ -82,6 +73,8 @@ namespace $ { export class $optimade_cifplayer_matinfio extends $mol_object2 { + static pos_overlap_limit = 0.1 + static log = this.$.$optimade_cifplayer_matinfio_log /** Guessing what to do */ diff --git a/matinfio/player/player.web.ts b/matinfio/player/player.web.ts index e71612e..d1c3ce7 100644 --- a/matinfio/player/player.web.ts +++ b/matinfio/player/player.web.ts @@ -37,63 +37,98 @@ namespace $ { mpds_data: crystal.mpds_data, mpds_demo: crystal.mpds_demo } - const pos2els: any = {} - const hashes: any = {} + const groups: { fpos: number[] | null, cpos: number[], atoms: $optimade_cifplayer_matinfio_internal_obj_atom[] }[] = [] + + // make atoms unique, i.e. remove collisions; for( let i = 0; i < crystal.atoms.length; i++ ) { - const pos = [ crystal.atoms[ i ].x, crystal.atoms[ i ].y, crystal.atoms[ i ].z ] - const hash = pos.map( function( item ) { return item.toFixed( 2 ) } ).join( ',' ) - // make atoms unique, i.e. remove collisions; - // makes special sense for partial occupancies - if( hashes.hasOwnProperty( hash ) ) { - var update = "" - for( let oprop in render.atoms[ hashes[ hash ] ].overlays ) { + const atom = crystal.atoms[ i ] + + const pos = [ atom.x, atom.y, atom.z ] + + // CIF has fractional positions + // OPTIMADE has cartesian positions + // POSCAR may have either of two + const fpos: number[] | null = crystal.cartesian + ? cell_matrix ? math.divide( pos, cell_matrix ).map( fract_cord_norm ) : null + : pos.map( fract_cord_norm ) + + const cpos: number[] = fpos ? math.multiply( fpos, cell_matrix ) : pos + + if( groups.some( group => { + if( is_overlap( cpos, group.cpos, $optimade_cifplayer_matinfio.pos_overlap_limit ) ) { + + const AseRadii = $optimade_cifplayer_matinfio_chemical_elements.AseRadii[ atom.symbol ] + const pos = group.atoms.findIndex( atom2 => { + return AseRadii > $optimade_cifplayer_matinfio_chemical_elements.AseRadii[ atom2.symbol ] + } ) + + if( pos == -1 ) group.atoms.push( atom ) + else group.atoms.splice( pos, 0, atom ) + + return true + + } + } ) ) { + continue + } + + groups.push( { fpos, cpos, atoms: [ atom ] } ) + } + + for( let i = 0; i < groups.length; i++ ) { + + const { fpos, cpos, atoms } = groups[i] + + const overlays: Record< string, string | number > = { + "S": atoms[0].symbol, + "N": i + 1, + } + for( let oprop in atoms[0].overlays ) { + overlays[ oprop ] = atoms[0].overlays[ oprop ] + } + + atoms.slice(1).forEach( atom => { + for( let oprop in overlays ) { + if( oprop == 'S' ) { - if( pos2els[ hash ].indexOf( crystal.atoms[ i ].symbol ) == -1 ) { - update = " " + crystal.atoms[ i ].symbol - pos2els[ hash ].push( crystal.atoms[ i ].symbol ) + if( atoms.every( a => a.symbol != atom.symbol ) ) { + overlays[ oprop ] += ' ' + atom.symbol } + + } else if( oprop == 'N' ) { + overlays[ oprop ] += ', ' + ( i + 1 ) + + } else if( oprop == '_atom_site_occupancy' ) { + overlays[ oprop ] += '+' + atom.overlays[ oprop ] + + } else { + overlays[ oprop ] += ' ' + atom.overlays[ oprop ] } - else if( oprop == 'N' ) - update = ", " + ( i + 1 ) - else if( oprop == '_atom_site_occupancy' ) - update = "+" + crystal.atoms[ i ].overlays[ oprop ] - else - update = " " + crystal.atoms[ i ].overlays[ oprop ] - - render.atoms[ hashes[ hash ] ].overlays[ oprop ] += update - } - } else { - const color = ($optimade_cifplayer_matinfio_chemical_elements.JmolColors as any)[ crystal.atoms[ i ].symbol ] || '#FFFF00' - const radius = ($optimade_cifplayer_matinfio_chemical_elements.AseRadii as any)[ crystal.atoms[ i ].symbol ] || 0.66 - - const overlays: Record< string, string | number > = { - "S": crystal.atoms[ i ].symbol, - "N": i + 1, - } - for( let oprop in crystal.atoms[ i ].overlays ) { - overlays[ oprop ] = crystal.atoms[ i ].overlays[ oprop ] + } + } ) - // CIF has fractional positions - // OPTIMADE has cartesian positions - // POSCAR may have either of two - const cpos = crystal.cartesian ? pos : math.multiply( pos, cell_matrix ) - const fpos = !crystal.cartesian ? pos : cell_matrix ? math.divide( pos, cell_matrix ) : null - const fract = fpos ? { 'x': fpos[ 0 ], 'y': fpos[ 1 ], 'z': fpos[ 2 ] } : null - - render.atoms.push( { - 'fract': fract, - 'x': cpos[ 0 ], - 'y': cpos[ 1 ], - 'z': cpos[ 2 ], - 'c': color, - 'r': radius, - 'overlays': overlays - } ) - hashes[ hash ] = render.atoms.length - 1 - pos2els[ hash ] = [ crystal.atoms[ i ].symbol ] + const color = $optimade_cifplayer_matinfio_chemical_elements.JmolColors[ atoms[0].symbol ] || '#FFFF00' + const radius = $optimade_cifplayer_matinfio_chemical_elements.AseRadii[ atoms[0].symbol ] || 0.66 + const atom_result = { + fract: fpos ? { + x: fpos[ 0 ], + y: fpos[ 1 ], + z: fpos[ 2 ], + } : null, + x: cpos[ 0 ], + y: cpos[ 1 ], + z: cpos[ 2 ], + c: color, + r: radius, + overlays, + symbol: atoms[0].symbol, + label: atoms[0].label, } + + render.atoms.push( atom_result ) + } for( let oprop in crystal.atoms.at(-1)!.overlays ) { @@ -102,5 +137,18 @@ namespace $ { return render } + + + function fract_cord_norm( cord: number ){ + const res = cord % 1 + return res > 0 ? res : res + 1 + } + + function is_overlap( pos1: number[], pos2: number[], threshold: number ) { + for( let i = 0; i < 3; i++ ) { + if ( pos1[i] < pos2[i] - threshold || pos1[i] > pos2[i] + threshold ) return false + } + return true + } } diff --git a/matinfio/spacegroup/spacegroup.web.ts b/matinfio/spacegroup/spacegroup.web.ts index 0b360bc..47b0f3e 100644 --- a/matinfio/spacegroup/spacegroup.web.ts +++ b/matinfio/spacegroup/spacegroup.web.ts @@ -42,9 +42,9 @@ namespace $ { const spans = symmetry.split( ',' ) const fract = { - x: fract_cord_norm( calc_symmetry_span( spans[ 0 ], atom.fract ) ), - y: fract_cord_norm( calc_symmetry_span( spans[ 1 ], atom.fract ) ), - z: fract_cord_norm( calc_symmetry_span( spans[ 2 ], atom.fract ) ), + x: fract_cord_norm( calc_symmetry_span( spans[ 0 ], atom.fract! ) ), + y: fract_cord_norm( calc_symmetry_span( spans[ 1 ], atom.fract! ) ), + z: fract_cord_norm( calc_symmetry_span( spans[ 2 ], atom.fract! ) ), } const [ x, y, z ] = math.multiply( [ fract.x, fract.y, fract.z ], cell ) @@ -96,9 +96,8 @@ namespace $ { } function fract_cord_norm( cord: number ){ - let res = cord % 1 - if( res < 0 ) res = res + 1 - return res + const res = cord % 1 + return res > 0 ? res : res + 1 } } diff --git a/player/player.view.web.ts b/player/player.view.web.ts index 5449a99..b3facf6 100644 --- a/player/player.view.web.ts +++ b/player/player.view.web.ts @@ -261,7 +261,7 @@ namespace $.$$ { for (const name of next_symmetries) { const atoms = this.symmetry_atoms( name )! - if( is_overlap( data, atoms, 0.01 ) ) { + if( is_overlap( data, atoms, $optimade_cifplayer_matinfio.pos_overlap_limit ) ) { return } }