Skip to content

Commit

Permalink
Typeset-side shape validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ceriottm committed Sep 26, 2023
1 parent 68f6102 commit 1d1e7d5
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 115 deletions.
4 changes: 2 additions & 2 deletions python/chemiscope/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
composition_properties,
ellipsoid_from_tensor,
extract_lammps_shapes_from_ase,
extract_tensors_from_ase,
extract_vectors_from_ase,
ase_vectors_to_arrows,
ase_tensors_to_ellipsoids,
extract_properties,
librascal_atomic_environments,
)
Expand Down
4 changes: 2 additions & 2 deletions python/chemiscope/input.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ def create_input(
Each of these can contain some or all of the parameters associated with each shape,
and the parameters for each shape are obtained by combining the parameters from the
most general to the most specific, i.e., if there is a duplicate key in the `global` and `atom`
fields, the value within the `atom` field will supercede the `global` field for that atom.
most general to the most specific, i.e., if there is a duplicate key in the `global` and `atom`
fields, the value within the `atom` field will supersede the `global` field for that atom.
The parameters for atom `k` that is part of structure `j` are obtained as
.. code-block:: python
Expand Down
4 changes: 2 additions & 2 deletions python/chemiscope/structures/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

from ._ase import ( # noqa isort: skip
extract_lammps_shapes_from_ase,
extract_tensors_from_ase,
extract_vectors_from_ase,
ase_vectors_to_arrows,
ase_tensors_to_ellipsoids,
)

from ._shapes import ( # noqa
Expand Down
6 changes: 3 additions & 3 deletions python/chemiscope/structures/_ase.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,10 @@ def ase_vectors_to_arrows(frames, key="forces", target=None, **kwargs):
return {"kind": "arrow", "parameters": {"global": globs, "structure": vectors}}


def ase_tensors_to_arrows(frames, key="tensor", target=None, **kwargs):
def ase_tensors_to_ellipsoids(frames, key="tensor", target=None, **kwargs):
"""
Extract a 3-tensor atom property from a list of ase.Atoms
objects, and returns a list of arrow shapes. Besides the specific
Extract a 2-tensor atom property from a list of ase.Atoms
objects, and returns a list of ellipsoids shapes. Besides the specific
parameters it also accepts the same parameters as
`ellipsoid_from_tensor`, which are used to draw the shapes
Expand Down
4 changes: 2 additions & 2 deletions python/examples/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
{
"position": [3, 2, 1],
"color": 0x00FF00,
"orientation": [0.2, 0.4, 0.1, 1],
"orientation": [0.5, -0.5, 0, 1 / np.sqrt(2)],
},
],
},
Expand Down Expand Up @@ -207,7 +207,7 @@
# (molecular) electric dipole
"dipole": dipoles_auto,
# atomic decomposition of the polarizability as ellipsoids. use utility to extract from the ASE frames
"alpha": chemiscope.ase_tensors_to_arrows(
"alpha": chemiscope.ase_tensors_to_ellipsoids(
frames, "alpha", force_positive=True, scale=0.2
),
# shapes with a bit of flair
Expand Down
203 changes: 107 additions & 96 deletions src/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* @module main
*/

import { CustomShape, Ellipsoid, Sphere } from './structure/shapes';
import { ShapeData, ShapeParameters } from './structure/shapes';
import { param } from 'jquery';

Check warning on line 6 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

'param' is defined but never used
import { CustomShape, Ellipsoid, Sphere, Arrow } from './structure/shapes';

Check failure on line 7 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Member 'Arrow' of the import declaration should be sorted alphabetically
import { ShapeParameters } from './structure/shapes';

/** A dataset containing all the data to be displayed. */
export interface Dataset {
Expand Down Expand Up @@ -241,9 +242,21 @@ export function validateDataset(o: JsObject): void {
}

if ('shapes' in o) {
checkShapes(o.shapes as Record<string, JsObject>, structureCount, envCount);

assignShapes(o.shapes as { [name: string]: ShapeParameters }, o.structures as Structure[]);
const check_shape = checkShapes(
o.shapes as Record<string, JsObject>,
structureCount,
envCount
);
if (check_shape != '') {

Check failure on line 250 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Expected '!==' and instead saw '!='
throw 'Error checking shape definitions: ' + check_shape;

Check failure on line 251 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Expected an error object to be thrown
}
const check_assign = assignShapes(
o.shapes as { [name: string]: ShapeParameters },
o.structures as Structure[]
);
if (check_assign != '') {

Check failure on line 257 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Expected '!==' and instead saw '!='
throw 'Error assigning shapes to structures: ' + check_assign;

Check failure on line 258 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Expected an error object to be thrown
}
}

if (!('properties' in o)) {
Expand Down Expand Up @@ -331,51 +344,82 @@ function checkStructures(o: JsObject[]): [number, number] {
}

function checkShapes(
properties: Record<string, JsObject>,
shapes: Record<string, JsObject>,
structureCount: number,
envCount: number,
parameters?: Record<string, JsObject> | undefined
) {
/*
// check to see if all structures have consistent shapes
// placed after structure check to ensure that all structures
// are first validated
if ('shapes' in o[0]) {
const shapeList = Object.keys(o[0].shapes as object);
for (let i = 0; i < o.length; i++) {
const structure = o[i];
if (!('shapes' in structure)) {
throw Error(`error in structure ${i}: "shape" is not defined`);
} else {
const shapes = structure['shapes'] as Record<string, unknown>;
for (const key of shapeList) {
if (!(key in shapes) || shapes[key] === undefined) {
throw Error(`error in structure ${i}: "${key}" is not defined`);
}
}
for (const key of Object.keys(shapes)) {
if (!shapeList.includes(key)) {
throw Error(
`error in structure ${i}: "${key}" is defined, but was not for previous structures`
);
}
}
envCount: number
): string {
if (typeof shapes !== 'object' || shapes === null) {
return "'shapes' must be an object";
}

for (const [key, shape] of Object.entries(shapes as object)) {
if (!('kind' in shape)) {
return `missing "kind" in shape ${key}`;
}

if (typeof shape.kind !== 'string') {

Check failure on line 360 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Unsafe member access .kind on an `any` value
return `shapes 'kind' must be a string for shape ${key}`;
}

if (
shape.kind !== 'sphere' &&

Check failure on line 365 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Unsafe member access .kind on an `any` value
shape.kind !== 'ellipsoid' &&

Check failure on line 366 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Unsafe member access .kind on an `any` value
shape.kind !== 'arrow' &&

Check failure on line 367 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Unsafe member access .kind on an `any` value
shape.kind !== 'custom'

Check failure on line 368 in src/dataset.ts

View workflow job for this annotation

GitHub Actions / npm-test (20.x)

Unsafe member access .kind on an `any` value
) {
return `Chemiscope currently only supports custom, ellipsoid, or sphere shapes, got ${shape.kind}`;
}

if (!('parameters' in shape)) {
return `missing "parameters" in shape ${key}`;
}

const parameters = shape.parameters as Record<string, ShapeParameters>;

if ('structure' in parameters) {
const s_parameters = parameters.structure;
if (!Array.isArray(s_parameters)) {
return `'structure' parameters should be an array in shape ${key}`;
}

if (s_parameters.length !== structureCount) {
return `'structure' parameters in shape ${key} contain ${s_parameters.length} entries, but there are ${structureCount} structures.`;
}
}
} else {
for (let i = 0; i < o.length; i++) {
const structure = o[i];
if ('shapes' in structure) {
throw Error(
`error in structure ${i}: "shape" is defined, but was not for previous structures`
);

if ('atom' in parameters) {
const a_parameters = parameters.atom;
if (!Array.isArray(a_parameters)) {
return `'atom' parameters should be an array in shape ${key}`;
}

if (a_parameters.length !== envCount) {
return `'atom' parameters in shape ${key} contain ${a_parameters.length} entries, but there are ${envCount} environments.`;
}
}
}
*/

return '';
}

function assignShapes(shapes: { [name: string]: ShapeParameters }, structures: Structure[]) {
function validateShape(kind: string, parameters: Record<string, unknown>): string {
if (kind === 'sphere') {
return Sphere.validateParameters(parameters);
} else if (kind === 'ellipsoid') {
return Ellipsoid.validateParameters(parameters);
} else if (kind === 'arrow') {
return Arrow.validateParameters(parameters);
} else if (kind === 'custom') {
return CustomShape.validateParameters(parameters);
}
return '';
}

function assignShapes(
shapes: { [name: string]: ShapeParameters },
structures: Structure[]
): string {
// creates shapes associated with actual structures by combining all the information given in the definition
let atomsCount = 0;
for (let i_structure = 0; i_structure < structures.length; i_structure++) {
const structure = structures[i_structure];
Expand All @@ -386,19 +430,40 @@ function assignShapes(shapes: { [name: string]: ShapeParameters }, structures: S
structure: shape.parameters.structure,
atom: shape.parameters.atom,
};

let full_parameters = shape.parameters.global;
if (parameters.structure) {
parameters.structure = [parameters.structure[i_structure]];
full_parameters = { ...full_parameters, ...parameters.structure[0] };
}

if (parameters.atom) {
parameters.atom = parameters.atom.slice(atomsCount, atomsCount + structure.size);

for (const atom of parameters.atom) {
const atom_parameters = { ...full_parameters, ...atom };
const check = validateShape(shape.kind, atom_parameters);
if (check !== '') {
return `Validation error for an atom in shape ${name}: ${check}`;
}
}
} else {
const check = validateShape(shape.kind, full_parameters);
if (check !== '') {
return `Validation error for a structure in shape ${name}: ${check}`;
}
}

structure.shapes[name] = {
kind: shape.kind,
parameters: parameters,
};
}

atomsCount += structure.size;
}

return ''; // success!
}

/**
Expand Down Expand Up @@ -435,60 +500,6 @@ export function checkStructure(s: JsObject): string {
}
}

if ('shapes' in s) {
const shapes = s.shapes;

if (typeof shapes !== 'object' || shapes === null) {
return "'shapes' must be an object";
}

for (const [key, array] of Object.entries(s.shapes as object)) {
/*
if (!Array.isArray(array)) {
return `shape['${key}'] must be an array`;
}
if (s.size > 0 && array.length !== s.size) {
return `wrong size for "shape['${key}']", expected ${s.size}, got ${array.length}`;
}
for (let i = 0; i < array.length; i++) {
const element = array[i] as unknown;
if (typeof element !== 'object' || element === null) {
return "'shapes' entries must be objects";
}
const shape = element as JsObject;
if (!('kind' in shape)) {
return `missing "kind" in shape for particle ${i}`;
}
if (typeof shape.kind !== 'string') {
return `shapes 'kind' must be a string for particle ${i}`;
}
if (shape.kind === 'sphere') {
const check = Sphere.validateParameters(shape);
if (check !== '') {
return check;
}
} else if (shape.kind === 'ellipsoid') {
const check = Ellipsoid.validateParameters(shape);
if (check !== '') {
return check;
}
} else if (shape.kind === 'custom') {
const check = CustomShape.validateParameters(shape);
if (check !== '') {
return check;
}
} else {
return `Chemiscope currently only supports custom, ellipsoid, or sphere shapes, got ${shape.kind}`;
}
} MCCOMMENT*/
}
}

return '';
}

Expand Down
9 changes: 1 addition & 8 deletions src/structure/shapes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,6 @@ export class Sphere extends Shape {
}

public static validateParameters(parameters: Record<string, unknown>): string {
assert(parameters.kind === 'sphere');

if (!('radius' in parameters)) {
return '"radius" is required for "sphere" shapes';
}
Expand Down Expand Up @@ -333,8 +331,6 @@ export class Ellipsoid extends Shape {
}

public static validateParameters(parameters: Record<string, unknown>): string {
assert(parameters.kind === 'ellipsoid');

if (!('semiaxes' in parameters)) {
return '"semiaxes" is required for "ellipsoid" shapes';
}
Expand Down Expand Up @@ -439,6 +435,7 @@ function triangulateArrow(
const vertices: XYZ[] = [];

vertices.push({ x: 0, y: 0, z: 0 });
// the arrow is built as a surface of revolution, by stacking _|\ motifs
for (let i = 0; i < resolution; i++) {
// nb replicated points are needed to get sharp edges
vertices.push(multXYZ(circle_points[i], base_radius));
Expand Down Expand Up @@ -500,8 +497,6 @@ export class Arrow extends Shape {
}

public static validateParameters(parameters: Record<string, unknown>): string {
assert(parameters.kind === 'arrow');

if (!('vector' in parameters)) {
return '"vector" is required for "arrow" shapes';
}
Expand Down Expand Up @@ -570,8 +565,6 @@ export class CustomShape extends Shape {
}

public static validateParameters(parameters: Record<string, unknown>): string {
assert(parameters.kind === 'custom');

if (!('vertices' in parameters)) {
return '"vertices" is required for "custom" shapes';
}
Expand Down

0 comments on commit 1d1e7d5

Please sign in to comment.