Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
uses: ./actions/yaml/lint
with:
python-version: 3.8
targets: ./assets

run-js-tests:
runs-on: ubuntu-latest
Expand Down
209 changes: 153 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"underscore.string": "^3.3.4"
},
"devDependencies": {
"@exabyte-io/code.js": "2023.9.14-1",
"@exabyte-io/application-flavors.js": "git+https://github.com/Exabyte-io/application-flavors.git#b3de5b1111d5f1300fe97cd6483265ac28640466",
"@exabyte-io/code.js": "git+https://github.com/Exabyte-io/code.js.git#9e19f7687e57e8b1469d11a7c28818dbb0bc1a94",
"@exabyte-io/eslint-config": "^2022.11.17-0",
"@exabyte-io/made.js": "2022.6.15-0",
"chai": "^4.3.4",
Expand All @@ -70,6 +71,7 @@
},
"peerDependencies": {
"@exabyte-io/ade.js": "*",
"@exabyte-io/application-flavors.js": "*",
"@exabyte-io/code.js": "*",
"@exabyte-io/made.js": "*"
},
Expand Down
24 changes: 0 additions & 24 deletions src/default_methods.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import lodash from "lodash";

import { MODEL_NAMES, MODEL_TREE } from "./tree";

export const PseudopotentialMethodConfig = {
type: "pseudopotential",
subtype: "us",
Expand All @@ -16,23 +12,3 @@ export const UnknownMethodConfig = {
type: "unknown",
subtype: "unknown",
};

export function allowedTypes(model) {
return lodash
.keys(lodash.get(MODEL_TREE, `${model.type}.${model.subtype}.methods`, []))
.map((x) => {
return {
slug: x,
name: lodash.get(MODEL_NAMES, x, x),
};
});
}

export function allowedSubtypes(model, type) {
return lodash.get(MODEL_TREE, `${model.type}.${model.subtype}.methods.${type}`, []).map((x) => {
return {
slug: x,
name: lodash.get(MODEL_NAMES, x, x),
};
});
}
62 changes: 62 additions & 0 deletions src/filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { filterEntityList, mergeTerminalNodes } from "@exabyte-io/code.js/dist/utils";
import lodash from "lodash";

import modelMethodMap from "./data/model_method_map";

function safelyGet(obj, ...args) {
return lodash.get(obj, args, undefined);
}

/**
* Create list of filter objects based on model categories.
* @param {Object} filterTree - filter tree constructed from assets
* @param {string} tier1 - Level 1 tier
* @param {string} tier2 - Level 2 tier
* @param {string} tier3 - Level 3 tier
* @param {string} type - Type
* @param {string} subtype - Subtype
* @return {*[]}
*/
function getMethodFilterObjects({ filterTree, tier1, tier2, tier3, type, subtype }) {
let filterList;
if (!tier1) {
filterList = mergeTerminalNodes(filterTree);
} else if (!tier2) {
filterList = mergeTerminalNodes(safelyGet(filterTree, tier1));
} else if (!tier3) {
filterList = mergeTerminalNodes(safelyGet(filterTree, tier1, tier2));
} else if (!type) {
filterList = mergeTerminalNodes(safelyGet(filterTree, tier1, tier2, tier3));
} else if (!subtype) {
filterList = mergeTerminalNodes(safelyGet(filterTree, tier1, tier2, tier3, type));
} else {
filterList = safelyGet(filterTree, tier1, tier2, tier3, type, subtype);
}
const extractUniqueBy = (name) => {
return lodash
.chain(filterList)
.filter(Boolean)
.filter((o) => Boolean(o[name]))
.uniqBy(name)
.value();
};

return [].concat(extractUniqueBy("path"), extractUniqueBy("regex"));
}

/**
* Filter list of method configs based on model
* @param {Object[]} methodList - Array of method configs
* @param {Object} model - Model config for which methods should be filtered
* @return {Object[]}
*/
export function filterMethodsByModel({ methodList, model }) {
if (!model) return [];
const { categories } = model;
const filterObjects = getMethodFilterObjects({ filterTree: modelMethodMap, ...categories });
return filterEntityList({
entitiesOrPaths: methodList,
filterObjects,
multiPathSeparator: "::",
});
}
9 changes: 5 additions & 4 deletions src/method.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { InMemoryEntity } from "@exabyte-io/code.js/dist/entity";
import { DefaultableInMemoryEntity } from "@exabyte-io/code.js/dist/entity";
import { deepClone } from "@exabyte-io/code.js/dist/utils";
import lodash from "lodash";
import isEmpty from "lodash/isEmpty";
import omit from "lodash/omit";

import { PseudopotentialMethodConfig } from "./default_methods";

export class Method extends InMemoryEntity {
export class Method extends DefaultableInMemoryEntity {
constructor(config) {
const data = config.data || {};
super({ ...config, data });
Expand Down Expand Up @@ -57,7 +58,7 @@ export class Method extends InMemoryEntity {

get omitInHashCalculation() {
const data = this.prop("data");
return !data.searchText && lodash.isEmpty(lodash.omit(data, "searchText"));
return !data.searchText && isEmpty(omit(data, "searchText"));
}

// data without client-only fields
Expand Down
6 changes: 3 additions & 3 deletions src/methods/pseudopotential.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { safeMakeArray } from "@exabyte-io/code.js/dist/utils";
import _ from "underscore";
import sortBy from "lodash/sortBy";

import { Method } from "../method";

Expand Down Expand Up @@ -71,7 +71,7 @@ export class PseudopotentialMethod extends Method {
// sort by alphabetical order to ensure elements order consistency in VASP POSCAR/POTCAR
this.setData({
...this.data,
pseudo: _.sortBy(pseudopotentials, "element").map((x) => x.toJSON()),
pseudo: sortBy(pseudopotentials, ["element"]).map((x) => x.toJSON()),
});
}

Expand All @@ -82,7 +82,7 @@ export class PseudopotentialMethod extends Method {
// sort by alphabetical order to ensure elements order consistency in VASP POSCAR/POTCAR
this.setData({
...this.data,
allPseudo: _.sortBy(pseudopotentials, "element").map((x) => x.toJSON()),
allPseudo: sortBy(pseudopotentials, ["element"]).map((x) => x.toJSON()),
});
}

Expand Down
77 changes: 34 additions & 43 deletions src/model.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { InMemoryEntity } from "@exabyte-io/code.js/dist/entity";
import { DefaultableInMemoryEntity } from "@exabyte-io/code.js/dist/entity";
import lodash from "lodash";

import { DFTModelConfig } from "./default_models";
import { Method } from "./method";
import { MethodFactory } from "./methods/factory";
import { getTreeByApplicationNameAndVersion, MODEL_TREE, treeSlugToNamedObject } from "./tree";
import { MethodInterface } from "./utils/method_interface";
import { ModelInterface } from "./utils/model_interface";

export class Model extends InMemoryEntity {
export class Model extends DefaultableInMemoryEntity {
constructor({ application, ...config }) {
super(config);
this._application = application;
this._MethodFactory = MethodFactory;
this._defaultCategorized = this.getDefaultCategorizedModel();
}

get type() {
Expand All @@ -26,37 +28,12 @@ export class Model extends InMemoryEntity {
this.setMethod(this._MethodFactory.create(this.defaultMethodConfig));
}

get allowedTypes() {
return Object.keys(this.tree).map((modelSlug) => treeSlugToNamedObject(modelSlug));
}

get allowedSubtypes() {
return Object.keys(this.treeBranchForType).map((slug) => treeSlugToNamedObject(slug));
}

get defaultType() {
return this.allowedTypes[0]?.slug;
return this._defaultConfig.type;
}

get defaultSubtype() {
return this.allowedSubtypes[0]?.slug;
}

get tree() {
return (this._application && this.treeByApplicationNameAndVersion) || MODEL_TREE;
}

get treeBranchForType() {
return this.tree[this.type] || {};
}

get treeBranchForSubType() {
return this.treeBranchForType[this.subtype] || {};
}

get treeByApplicationNameAndVersion() {
const [name, version] = [this._application.name, this._application.version];
return getTreeByApplicationNameAndVersion({ name, version });
return this._defaultConfig.subtype;
}

get groupSlug() {
Expand All @@ -75,23 +52,29 @@ export class Model extends InMemoryEntity {
this._method = method;
}

// Consider moving the below to `Method`
get methodsFromTree() {
return this.treeBranchForSubType.methods || {};
getCategorizedMethods() {
const catModel = ModelInterface.convertToCategorized(this._json);
return MethodInterface.filterCategorizedMethods({
application: this._application,
model: catModel,
});
}

get methodTypes() {
return Object.keys(this.methodsFromTree).map((m) => treeSlugToNamedObject(m));
get defaultMethodConfig() {
const [defaultCategorizedMethod] = this.getCategorizedMethods();
return MethodInterface.convertToSimple(defaultCategorizedMethod);
}

get methodSubtypes() {
return this.methodsFromTree[this.method.type].map((m) => treeSlugToNamedObject(m)) || [];
/**
* @returns {{ path: string }[]} - Array of categorized models
*/
getCategorizedModels() {
return ModelInterface.filterCategorizedModels(this._application);
}

get defaultMethodConfig() {
const type = Object.keys(this.methodsFromTree)[0];
const subtype = (this.methodsFromTree[type] || [])[0];
return { type, subtype };
getDefaultCategorizedModel() {
if (this.type === "unknown") return undefined;
return this.getCategorizedModels()[0];
}

static get defaultConfig() {
Expand All @@ -101,8 +84,16 @@ export class Model extends InMemoryEntity {
};
}

static get allTypes() {
return Object.keys(this.tree).map((modelSlug) => treeSlugToNamedObject(modelSlug));
/**
* Instance method for default config.
* This is used to obtain default properties such as type/subtype.
*/
get _defaultConfig() {
// TODO: Add suitable method, given application/model
const defaultModel = ModelInterface.convertToSimple(this._defaultCategorized);
return {
...defaultModel,
};
}

toJSON() {
Expand Down
57 changes: 25 additions & 32 deletions src/models/dft.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import _ from "underscore";
import { filterEntityList } from "@exabyte-io/code.js/dist/utils";

import { MethodFactory } from "../methods/factory";
import { Model } from "../model";
import { treeSlugToNamedObject } from "../tree";
import { safelyGetSlug, stringToSlugifiedEntry } from "../utils/slugifiedEntry";

export class DFTModel extends Model {
constructor(config) {
Expand All @@ -29,7 +29,10 @@ export class DFTModel extends Model {
}

get defaultFunctional() {
return treeSlugToNamedObject(this.treeBranchForSubType.functionals[0]);
const functional = this._defaultConfig?.functional
? safelyGetSlug(this._defaultConfig?.functional)
: "pbe";
return stringToSlugifiedEntry(functional);
}

// eslint-disable-next-line class-methods-use-this
Expand All @@ -43,7 +46,8 @@ export class DFTModel extends Model {
}

get functional() {
return this.prop("functional", this.defaultFunctional);
const functional = this.prop("functional", this.defaultFunctional);
return typeof functional === "string" ? stringToSlugifiedEntry(functional) : functional;
}

get refiners() {
Expand All @@ -54,19 +58,32 @@ export class DFTModel extends Model {
return this.prop("modifiers", this.defaultModifiers);
}

getDefaultCategorizedModel() {
const subtype = this.prop("subtype", "gga");

const filterObj = {
regex: "\\/pb\\/qm\\/dft\\/ksdft\\/" + subtype + "\\?functional=([A-Za-z0-9_-]+)",
};
const filtered = filterEntityList({
entitiesOrPaths: this.getCategorizedModels(),
filterObjects: [filterObj],
});
return filtered[0];
}

setSubtype(subtype) {
this.setProp("subtype", subtype);
this.setFunctional(this.defaultFunctional);
}

setFunctional(functional) {
this.setProp("functional", this._stringToSlugifiedObject(functional));
this.setProp("functional", stringToSlugifiedEntry(functional));
this.setMethod(this._MethodFactory.create(this.defaultMethodConfig));
}

_setArrayProp(name, data) {
// eslint-disable-next-line no-param-reassign, no-undef
data = safeMakeArray(data).map((r) => this._stringToSlugifiedObject(r));
data = safeMakeArray(data).map((r) => stringToSlugifiedEntry(r));
this.setProp(name, data);
this[`_${name}`] = data;
}
Expand All @@ -80,36 +97,12 @@ export class DFTModel extends Model {
}

toJSON() {
const pickSlugFromObject = (o) => _.pick(o, "slug");
const functional = safelyGetSlug(this.functional);
return {
...super.toJSON(),
functional: pickSlugFromObject(this.functional), // only store slug for `functional`
functional,
refiners: this.refiners,
modifiers: this.modifiers,
};
}

/**
* Get all functionals in the form of {name: ..., slug: ...} for further use in UI
* @returns {Object.<string, string>[]} - List of functional objects
*/
get allFunctionals() {
return this.treeBranchForSubType.functionals.map((x) => treeSlugToNamedObject(x));
}

/**
* Get all refiners in the form of {name: ..., slug: ...} for further use in UI
* @returns {Object.<string, string>[]} - List of refiner objects
*/
get allRefiners() {
return this.treeBranchForSubType.refiners.map((x) => treeSlugToNamedObject(x));
}

/**
* Get all modifiers in the form of {name: ..., slug: ...} for further use in UI
* @returns {Object.<string, string>[]} - List of modifier objects
*/
get allModifiers() {
return this.treeBranchForSubType.modifiers.map((x) => treeSlugToNamedObject(x));
}
}
Loading