@mat3ra/mode 2025.10.2-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ import { safeMakeArray } from "@mat3ra/code/dist/js/utils";
2
+ import _ from "underscore";
3
+
4
+ import { Method } from "../method";
5
+
6
+ export class PseudopotentialMethod extends Method {
7
+ constructor(config) {
8
+ super(config);
9
+ this.PseudopotentialCls = null;
10
+ }
11
+
12
+ // "Unique" or "selected" pseudopotentials - one per element
13
+ get pseudo() {
14
+ return this.prop("data.pseudo", []);
15
+ }
16
+
17
+ // "All" or "non-unique" pseudopotentials available for a choice
18
+ get allPseudo() {
19
+ return this.prop("data.allPseudo", []);
20
+ }
21
+
22
+ get pseudopotentials() {
23
+ return this.pseudo.map((x) => new this.PseudopotentialCls(x));
24
+ }
25
+
26
+ get allPseudopotentials() {
27
+ return this.allPseudo.map((x) => new this.PseudopotentialCls(x));
28
+ }
29
+
30
+ static extractExchangeCorrelationFromSubworkflow(subworkflow) {
31
+ const { model } = subworkflow;
32
+ const approximation = model.subtype;
33
+ const functional = model.functional ? model.functional.slug || model.functional : "";
34
+ return {
35
+ approximation,
36
+ functional,
37
+ };
38
+ }
39
+
40
+ hasPseudopotentialFor(element) {
41
+ return Boolean(this.pseudopotentials.find((x) => x.element === element));
42
+ }
43
+
44
+ /**
45
+ * @summary Sets one pseudopotential per element. Subsequent applications will override previous.
46
+ * @param pseudo {Pseudopotential}
47
+ */
48
+ setPseudopotentialPerElement(pseudo) {
49
+ if (!pseudo) {
50
+ this.setPseudopotentials([]);
51
+ return;
52
+ }
53
+ const arr = this.pseudopotentials.filter((x) => x.element !== pseudo.element);
54
+ arr.push(pseudo);
55
+ this.setPseudopotentials(arr);
56
+ }
57
+
58
+ // add new pseudopotentials to the list of all pseudopotentials
59
+ addToAllPseudos(pseudos) {
60
+ // eslint-disable-next-line no-param-reassign
61
+ pseudos = safeMakeArray(pseudos);
62
+ const allPseudos = this.allPseudopotentials;
63
+ allPseudos.push(...pseudos);
64
+ this.setAllPseudopotentials(allPseudos);
65
+ }
66
+
67
+ /**
68
+ * @param pseudopotentials {Pseudopotential[]}
69
+ */
70
+ setPseudopotentials(pseudopotentials) {
71
+ // sort by alphabetical order to ensure elements order consistency in VASP POSCAR/POTCAR
72
+ this.setData({
73
+ ...this.data,
74
+ pseudo: _.sortBy(pseudopotentials, "element").map((x) => x.toJSON()),
75
+ });
76
+ }
77
+
78
+ /**
79
+ * @param pseudopotentials {Pseudopotential[]}
80
+ */
81
+ setAllPseudopotentials(pseudopotentials) {
82
+ // sort by alphabetical order to ensure elements order consistency in VASP POSCAR/POTCAR
83
+ this.setData({
84
+ ...this.data,
85
+ allPseudo: _.sortBy(pseudopotentials, "element").map((x) => x.toJSON()),
86
+ });
87
+ }
88
+
89
+ // override default `toJSONWithCleanData` method to avoid saving `allPseudo` inside the workflow
90
+ // TODO: revise after implementing Pseudopotential Sets
91
+ toJSONWithCleanData(exclude = []) {
92
+ return super.toJSONWithCleanData(
93
+ exclude.concat([
94
+ "allPseudo",
95
+ // 'pseudo' - not cleaning the field to save it in `Job.workflow`
96
+ ]),
97
+ );
98
+ }
99
+ }
package/src/model.js ADDED
@@ -0,0 +1,127 @@
1
+ import { InMemoryEntity } from "@mat3ra/code/dist/js/entity";
2
+ import lodash from "lodash";
3
+
4
+ import { DFTModelConfig } from "./default_models";
5
+ import { Method } from "./method";
6
+ import { MethodFactory } from "./methods/factory";
7
+ import { getTreeByApplicationNameAndVersion, MODEL_TREE, treeSlugToNamedObject } from "./tree";
8
+
9
+ export class Model extends InMemoryEntity {
10
+ constructor({ application, ...config }) {
11
+ super(config);
12
+ this._application = application;
13
+ this._MethodFactory = MethodFactory;
14
+ }
15
+
16
+ get type() {
17
+ return this.prop("type", this.defaultType);
18
+ }
19
+
20
+ get subtype() {
21
+ return this.prop("subtype", this.defaultSubtype);
22
+ }
23
+
24
+ setSubtype(subtype) {
25
+ this.setProp("subtype", subtype);
26
+ this.setMethod(this._MethodFactory.create(this.defaultMethodConfig));
27
+ }
28
+
29
+ get allowedTypes() {
30
+ return Object.keys(this.tree).map((modelSlug) => treeSlugToNamedObject(modelSlug));
31
+ }
32
+
33
+ get allowedSubtypes() {
34
+ return Object.keys(this.treeBranchForType).map((slug) => treeSlugToNamedObject(slug));
35
+ }
36
+
37
+ get defaultType() {
38
+ return this.allowedTypes[0]?.slug;
39
+ }
40
+
41
+ get defaultSubtype() {
42
+ return this.allowedSubtypes[0]?.slug;
43
+ }
44
+
45
+ get tree() {
46
+ return (this._application && this.treeByApplicationNameAndVersion) || MODEL_TREE;
47
+ }
48
+
49
+ get treeBranchForType() {
50
+ return this.tree[this.type] || {};
51
+ }
52
+
53
+ get treeBranchForSubType() {
54
+ return this.treeBranchForType[this.subtype] || {};
55
+ }
56
+
57
+ get treeByApplicationNameAndVersion() {
58
+ const [name, version] = [this._application.name, this._application.version];
59
+ return getTreeByApplicationNameAndVersion({ name, version });
60
+ }
61
+
62
+ get groupSlug() {
63
+ return `${this._application.shortName}:${this.type}:${this.subtype}`;
64
+ }
65
+
66
+ get method() {
67
+ if (!this._method) {
68
+ const method = this.prop("method") || this.defaultMethodConfig;
69
+ this._method = this._MethodFactory.create(method);
70
+ }
71
+ return this._method;
72
+ }
73
+
74
+ setMethod(method) {
75
+ this._method = method;
76
+ }
77
+
78
+ // Consider moving the below to `Method`
79
+ get methodsFromTree() {
80
+ return this.treeBranchForSubType.methods || {};
81
+ }
82
+
83
+ get methodTypes() {
84
+ return Object.keys(this.methodsFromTree).map((m) => treeSlugToNamedObject(m));
85
+ }
86
+
87
+ get methodSubtypes() {
88
+ return this.methodsFromTree[this.method.type].map((m) => treeSlugToNamedObject(m)) || [];
89
+ }
90
+
91
+ get defaultMethodConfig() {
92
+ const type = Object.keys(this.methodsFromTree)[0];
93
+ const subtype = (this.methodsFromTree[type] || [])[0];
94
+ return { type, subtype };
95
+ }
96
+
97
+ static get defaultConfig() {
98
+ return {
99
+ ...DFTModelConfig,
100
+ method: Method.defaultConfig,
101
+ };
102
+ }
103
+
104
+ static get allTypes() {
105
+ return Object.keys(this.tree).map((modelSlug) => treeSlugToNamedObject(modelSlug));
106
+ }
107
+
108
+ toJSON() {
109
+ return {
110
+ ...super.toJSON(),
111
+ type: this.type,
112
+ subtype: this.subtype,
113
+ // TODO: use schema-based cleaning instead
114
+ method: this.method.toJSONWithCleanData(),
115
+ };
116
+ }
117
+
118
+ // to be used with extra properties
119
+ // eslint-disable-next-line class-methods-use-this
120
+ _stringToSlugifiedObject(slug) {
121
+ return lodash.isString(slug) ? { slug } : slug;
122
+ }
123
+
124
+ get isUnknown() {
125
+ return this.type === "unknown";
126
+ }
127
+ }
@@ -0,0 +1,115 @@
1
+ import _ from "underscore";
2
+
3
+ import { MethodFactory } from "../methods/factory";
4
+ import { Model } from "../model";
5
+ import { treeSlugToNamedObject } from "../tree";
6
+
7
+ export class DFTModel extends Model {
8
+ constructor(config) {
9
+ super(config);
10
+ this._MethodFactory = config.MethodFactory || MethodFactory;
11
+ }
12
+
13
+ /**
14
+ * @summary Build slug string based on model information
15
+ * @returns {string}
16
+ */
17
+ get groupSlug() {
18
+ return [
19
+ this._application.shortName,
20
+ this.type,
21
+ this.subtype,
22
+ this.functional.slug,
23
+ this.refiners.map((o) => o.slug).join("+"),
24
+ this.modifiers.map((o) => o.slug).join("+"),
25
+ ]
26
+ .join(":")
27
+ .replace("::", ":")
28
+ .replace(/:$/, "");
29
+ }
30
+
31
+ get defaultFunctional() {
32
+ return treeSlugToNamedObject(this.treeBranchForSubType.functionals[0]);
33
+ }
34
+
35
+ // eslint-disable-next-line class-methods-use-this
36
+ get defaultRefiners() {
37
+ return [];
38
+ }
39
+
40
+ // eslint-disable-next-line class-methods-use-this
41
+ get defaultModifiers() {
42
+ return [];
43
+ }
44
+
45
+ get functional() {
46
+ return this.prop("functional", this.defaultFunctional);
47
+ }
48
+
49
+ get refiners() {
50
+ return this.prop("refiners", this.defaultRefiners);
51
+ }
52
+
53
+ get modifiers() {
54
+ return this.prop("modifiers", this.defaultModifiers);
55
+ }
56
+
57
+ setSubtype(subtype) {
58
+ this.setProp("subtype", subtype);
59
+ this.setFunctional(this.defaultFunctional);
60
+ }
61
+
62
+ setFunctional(functional) {
63
+ this.setProp("functional", this._stringToSlugifiedObject(functional));
64
+ this.setMethod(this._MethodFactory.create(this.defaultMethodConfig));
65
+ }
66
+
67
+ _setArrayProp(name, data) {
68
+ // eslint-disable-next-line no-param-reassign, no-undef
69
+ data = safeMakeArray(data).map((r) => this._stringToSlugifiedObject(r));
70
+ this.setProp(name, data);
71
+ this[`_${name}`] = data;
72
+ }
73
+
74
+ setRefiners(refiners) {
75
+ this._setArrayProp("refiners", refiners);
76
+ }
77
+
78
+ setModifiers(modifiers) {
79
+ this._setArrayProp("modifiers", modifiers);
80
+ }
81
+
82
+ toJSON() {
83
+ const pickSlugFromObject = (o) => _.pick(o, "slug");
84
+ return {
85
+ ...super.toJSON(),
86
+ functional: pickSlugFromObject(this.functional), // only store slug for `functional`
87
+ refiners: this.refiners,
88
+ modifiers: this.modifiers,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Get all functionals in the form of {name: ..., slug: ...} for further use in UI
94
+ * @returns {Object.<string, string>[]} - List of functional objects
95
+ */
96
+ get allFunctionals() {
97
+ return this.treeBranchForSubType.functionals.map((x) => treeSlugToNamedObject(x));
98
+ }
99
+
100
+ /**
101
+ * Get all refiners in the form of {name: ..., slug: ...} for further use in UI
102
+ * @returns {Object.<string, string>[]} - List of refiner objects
103
+ */
104
+ get allRefiners() {
105
+ return this.treeBranchForSubType.refiners.map((x) => treeSlugToNamedObject(x));
106
+ }
107
+
108
+ /**
109
+ * Get all modifiers in the form of {name: ..., slug: ...} for further use in UI
110
+ * @returns {Object.<string, string>[]} - List of modifier objects
111
+ */
112
+ get allModifiers() {
113
+ return this.treeBranchForSubType.modifiers.map((x) => treeSlugToNamedObject(x));
114
+ }
115
+ }
@@ -0,0 +1,28 @@
1
+ import { Model } from "../model";
2
+ import { getDefaultModelTypeForApplication } from "../tree";
3
+ import { DFTModel } from "./dft";
4
+
5
+ export class ModelFactory {
6
+ static DFTModel = DFTModel;
7
+
8
+ static Model = Model;
9
+
10
+ static create(config) {
11
+ switch (config.type) {
12
+ case "dft":
13
+ return new this.DFTModel(config);
14
+ default:
15
+ return new this.Model(config);
16
+ }
17
+ }
18
+
19
+ static createFromApplication(config) {
20
+ const { application } = config;
21
+ const type = application && getDefaultModelTypeForApplication(application);
22
+ if (!type)
23
+ throw new Error(
24
+ `ModelFactory.createFromApplication: cannot determine model type: ${type}`,
25
+ );
26
+ return this.create({ ...config, type });
27
+ }
28
+ }
package/src/tree.js ADDED
@@ -0,0 +1,175 @@
1
+ import { deepClone } from "@mat3ra/code/dist/js/utils";
2
+ import { ModelMethodFilter } from "@mat3ra/standata";
3
+ import lodash from "lodash";
4
+ import _ from "underscore";
5
+
6
+ // TODO: migrate to use manifest instead
7
+
8
+ export const METHODS = {
9
+ pseudopotential: "pseudopotential",
10
+ localorbital: "localorbital",
11
+ unknown: "unknown",
12
+ };
13
+
14
+ const methods = {
15
+ [METHODS.pseudopotential]: ["paw", "nc", "nc-fr", "us"],
16
+ // TODO: Add additional basis set options, once user choice of specific (i.e 3-21G vs cc-pVDZ) is implemented.
17
+ [METHODS.localorbital]: ["pople"],
18
+ [METHODS.unknown]: ["unknown"],
19
+ };
20
+
21
+ export const getPseudopotentialTypesFromTree = () => methods[METHODS.pseudopotential];
22
+
23
+ // DFT-specific
24
+
25
+ const DFTModelRefiners = ["hse", "g0w0"];
26
+ const DFTModelModifiers = ["soc", "magn"];
27
+
28
+ const DFTModelTree = {
29
+ gga: {
30
+ refiners: DFTModelRefiners,
31
+ modifiers: DFTModelModifiers,
32
+ methods,
33
+ functionals: ["pbe", "pbesol", "pw91", "other"],
34
+ },
35
+ lda: {
36
+ refiners: DFTModelRefiners,
37
+ modifiers: DFTModelModifiers,
38
+ methods,
39
+ functionals: ["pz", "pw", "vwn", "other"],
40
+ },
41
+ hybrid: {
42
+ methods,
43
+ functionals: ["b3lyp", "hse06"],
44
+ },
45
+ other: {
46
+ methods,
47
+ functionals: ["other"],
48
+ },
49
+ };
50
+
51
+ export const getDFTFunctionalsFromTree = () => Object.keys(DFTModelTree);
52
+
53
+ export const getDFTFunctionalsByApproximation = (approximation) => {
54
+ const branch = DFTModelTree[approximation];
55
+ return branch && branch.functionals;
56
+ };
57
+
58
+ // GENERAL
59
+
60
+ export const MODEL_TREE = {
61
+ dft: DFTModelTree,
62
+ ml: {
63
+ re: {
64
+ methods: {
65
+ linear: ["least_squares", "ridge"],
66
+ kernel_ridge: ["least_squares"],
67
+ },
68
+ },
69
+ },
70
+ unknown: {
71
+ unknown: {
72
+ methods: {
73
+ unknown: ["unknown"],
74
+ },
75
+ },
76
+ },
77
+ };
78
+
79
+ export const MODEL_NAMES = {
80
+ dft: "density functional theory",
81
+ lda: "local density approximation",
82
+ gga: "generalized gradient approximation",
83
+ hybrid: "hybrid functional",
84
+ ml: "machine learning",
85
+ re: "regression",
86
+ };
87
+
88
+ export const treeSlugToNamedObject = (modelSlug) => {
89
+ return {
90
+ slug: modelSlug,
91
+ name: lodash.get(MODEL_NAMES, modelSlug, modelSlug),
92
+ };
93
+ };
94
+
95
+ // TODO: find a better way to handle application-specific model-method combination
96
+ // must be a subset of the MODEL_TREE above
97
+ // demonstrate how tree can be modified
98
+ // VASP_MODELS_TREE.gga.functionals = _.omit(VASP_MODELS_TREE.gga.functionals);
99
+
100
+ const VASP_MODELS_TREE = deepClone(_.pick(MODEL_TREE, "dft"));
101
+ const ESPRESSO_MODELS_TREE = deepClone(_.pick(MODEL_TREE, "dft"));
102
+ const NWCHEM_MODELS_TREE = deepClone(_.pick(MODEL_TREE, "dft"));
103
+
104
+ ["gga", "lda"].forEach((approximation) => {
105
+ // pick "paw" for vasp
106
+ VASP_MODELS_TREE.dft[approximation].methods.pseudopotential = VASP_MODELS_TREE.dft[
107
+ approximation
108
+ ].methods.pseudopotential.splice(0, 1);
109
+
110
+ // assert "us" is the first option
111
+ ESPRESSO_MODELS_TREE.dft[approximation].methods.pseudopotential =
112
+ ESPRESSO_MODELS_TREE.dft[approximation].methods.pseudopotential.reverse();
113
+ });
114
+
115
+ const UNKNOWN_MODELS_TREE = _.pick(MODEL_TREE, "unknown");
116
+ // const ML_MODELS_TREE = _.pick(MODEL_TREE, "ml");
117
+
118
+ const MODELS_TREE_CONFIGS_BY_APPLICATION_NAME_VERSION = [
119
+ {
120
+ name: "vasp",
121
+ tree: VASP_MODELS_TREE,
122
+ },
123
+ {
124
+ name: "espresso",
125
+ tree: ESPRESSO_MODELS_TREE,
126
+ },
127
+ {
128
+ name: "python",
129
+ tree: UNKNOWN_MODELS_TREE,
130
+ },
131
+ {
132
+ name: "shell",
133
+ tree: UNKNOWN_MODELS_TREE,
134
+ },
135
+ {
136
+ name: "jupyterLab",
137
+ tree: UNKNOWN_MODELS_TREE,
138
+ },
139
+ {
140
+ name: "nwchem",
141
+ tree: NWCHEM_MODELS_TREE,
142
+ },
143
+ {
144
+ name: "deepmd",
145
+ tree: UNKNOWN_MODELS_TREE,
146
+ },
147
+ ];
148
+
149
+ export const getTreeByApplicationNameAndVersion = ({
150
+ name,
151
+ // eslint-disable-next-line no-unused-vars
152
+ version,
153
+ }) => {
154
+ // TODO: add logic to filter by version when necessary
155
+ const cfgs = MODELS_TREE_CONFIGS_BY_APPLICATION_NAME_VERSION.filter(
156
+ (cfg) => cfg.name === name,
157
+ ).map((x) => x.tree);
158
+ return Object.assign({}, ...cfgs);
159
+ };
160
+
161
+ export const getDefaultModelTypeForApplication = (application) => {
162
+ return Object.keys(getTreeByApplicationNameAndVersion(application))[0];
163
+ };
164
+
165
+ /**
166
+ * Filter list of method configs based on model
167
+ * @param {Object[]} methodList - Array of method configs
168
+ * @param {Object} model - Model config for which methods should be filtered
169
+ * @return {Object[]}
170
+ */
171
+ export function filterMethodsByModel({ methodList, model }) {
172
+ if (!model) return [];
173
+ const modelMethodFilter = new ModelMethodFilter();
174
+ return modelMethodFilter.getCompatibleMethods(model, methodList);
175
+ }