diff --git a/package-lock.json b/package-lock.json index fe06d532..6b93974c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,13 +28,13 @@ "v20": "^0.1.0" }, "devDependencies": { - "@exabyte-io/ade.js": "2025.7.15-0", + "@exabyte-io/ade.js": "2025.7.15-1", "@exabyte-io/application-flavors.js": "2025.7.8-0", "@exabyte-io/eslint-config": "2025.5.13-0", "@exabyte-io/ide.js": "2024.3.26-0", "@exabyte-io/mode.js": "2024.4.28-0", "@mat3ra/code": "2025.7.15-0", - "@mat3ra/esse": "2025.4.26-0", + "@mat3ra/esse": "2025.7.15-0", "@mat3ra/made": "2025.7.15-0", "chai": "^4.3.4", "eslint": "7.32.0", @@ -2383,9 +2383,9 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, "node_modules/@exabyte-io/ade.js": { - "version": "2025.7.15-0", - "resolved": "https://registry.npmjs.org/@exabyte-io/ade.js/-/ade.js-2025.7.15-0.tgz", - "integrity": "sha512-ivItgWJyaourwpRPuxcT+qENfPBkKlSmXzu20zRBrl6lG2iGcTXNsaUh/PKzrWbfzKegx5GADiPdygpHcuWgww==", + "version": "2025.7.15-1", + "resolved": "https://registry.npmjs.org/@exabyte-io/ade.js/-/ade.js-2025.7.15-1.tgz", + "integrity": "sha512-w2anDB6p9+O/ioiNRYI8ykewi56vwV9+u/hO+KvgHI0TBPaFXmYbQkl73lnekm3QwWbqHSA/G5JSQ52Zez+Ttw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2851,10 +2851,11 @@ } }, "node_modules/@mat3ra/esse": { - "version": "2025.4.26-0", - "resolved": "https://registry.npmjs.org/@mat3ra/esse/-/esse-2025.4.26-0.tgz", - "integrity": "sha512-0xWNAZTTM6F7Gv1FGMWjs/6cnLVKsIKLwYpQqs5Tll/gqF9KwXdO6l3GJznjDZCUy1lrWgvBrHaD0NBGjKa5Iw==", + "version": "2025.7.15-0", + "resolved": "https://registry.npmjs.org/@mat3ra/esse/-/esse-2025.7.15-0.tgz", + "integrity": "sha512-q/oeMrx1wKGFB/e9USu+jzrKDVc0HEBz4Igar7VMIfvXEI/ucNq4Esd4chVkSalf/0BT4jbCJrL8PLojWMd7mg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@babel/cli": "^7.27.0", "@babel/core": "^7.26.10", diff --git a/package.json b/package.json index b97627f0..1099d860 100644 --- a/package.json +++ b/package.json @@ -49,13 +49,13 @@ "v20": "^0.1.0" }, "devDependencies": { - "@exabyte-io/ade.js": "2025.7.15-0", + "@exabyte-io/ade.js": "2025.7.15-1", "@exabyte-io/application-flavors.js": "2025.7.8-0", "@exabyte-io/eslint-config": "2025.5.13-0", "@exabyte-io/ide.js": "2024.3.26-0", "@exabyte-io/mode.js": "2024.4.28-0", "@mat3ra/code": "2025.7.15-0", - "@mat3ra/esse": "2025.4.26-0", + "@mat3ra/esse": "2025.7.15-0", "@mat3ra/made": "2025.7.15-0", "chai": "^4.3.4", "eslint": "7.32.0", diff --git a/src/subworkflows/create.js b/src/subworkflows/create.js index cb8f4362..3ee240a9 100644 --- a/src/subworkflows/create.js +++ b/src/subworkflows/create.js @@ -1,4 +1,4 @@ -import { Application } from "@exabyte-io/ade.js"; +import ApplicationRegistry from "@exabyte-io/ade.js/dist/js/ApplicationRegistry"; import { default_methods as MethodConfigs, default_models as ModelConfigs, @@ -14,17 +14,6 @@ import { workflowData as allWorkflowData } from "../workflows/workflows"; import { dynamicSubworkflowsByApp, getSurfaceEnergySubworkflowUnits } from "./dynamic"; import { Subworkflow } from "./subworkflow"; -/** - * @summary Thin wrapper around Application.createFromStored for extensibility - * @param config {Object} application config - * @param applicationCls {any} application class - * @returns {Application} the application - */ -function createApplication({ config, applicationCls }) { - const { name, version, build = "Default" } = config; - return applicationCls.create({ name, version, build }); -} - // NOTE: DFTModel => DFTModelConfig, configs should have the same name as the model/method class + "Config" at the end function _getConfigFromModelOrMethodName(name, kind) { const configs = kind === "Model" ? ModelConfigs : MethodConfigs; @@ -63,14 +52,14 @@ function createMethod({ config, methodFactoryCls }) { /** * @summary Create top-level objects used in subworkflow initialization * @param subworkflowData {Object} subworkflow data - * @param applicationCls {any} application class + * @param AppRegistry * @param modelFactoryCls {any} model factory class * @param methodFactoryCls {any} method factory class * @returns {{application: *, method: *, model: (DFTModel|Model), setSearchText: String|null}} */ -function createTopLevel({ subworkflowData, applicationCls, modelFactoryCls, methodFactoryCls }) { +function createTopLevel({ subworkflowData, modelFactoryCls, methodFactoryCls, AppRegistry }) { const { application: appConfig, model: modelConfig, method: methodConfig } = subworkflowData; - const application = createApplication({ config: appConfig, applicationCls }); + const application = AppRegistry.createApplication(appConfig); const model = createModel({ config: modelConfig, modelFactoryCls }); const { method, setSearchText } = createMethod({ config: methodConfig, methodFactoryCls }); return { @@ -91,7 +80,7 @@ function createTopLevel({ subworkflowData, applicationCls, modelFactoryCls, meth * @param unitFactoryCls {*} workflow unit class factory * @returns {*|{head: boolean, preProcessors: [], postProcessors: [], name: *, flowchartId: *, type: *, results: [], monitors: []}} */ -function createUnit({ config, application, unitBuilders, unitFactoryCls }) { +export function createUnit({ config, application, unitBuilders, unitFactoryCls }) { const { type, config: unitConfig } = config; if (type === "executionBuilder") { const { name, execName, flavorName, flowchartId } = unitConfig; @@ -143,7 +132,7 @@ function createDynamicUnits({ function createSubworkflow({ subworkflowData, - applicationCls = Application, + AppRegistry = ApplicationRegistry, modelFactoryCls = ModelFactory, methodFactoryCls = MethodFactory, subworkflowCls = Subworkflow, @@ -152,7 +141,7 @@ function createSubworkflow({ }) { const { application, model, method, setSearchText } = createTopLevel({ subworkflowData, - applicationCls, + AppRegistry, modelFactoryCls, methodFactoryCls, }); diff --git a/src/units/base.js b/src/units/base.js index 98b0dfc8..c97690b5 100644 --- a/src/units/base.js +++ b/src/units/base.js @@ -1,17 +1,12 @@ -import { - NamedDefaultableRepetitionRuntimeItemsImportantSettingsContextAndRenderHashedInMemoryEntity, - TaggableMixin, -} from "@mat3ra/code/dist/js/entity"; +import { NamedDefaultableRepetitionRuntimeItemsImportantSettingsContextAndRenderHashedInMemoryEntity } from "@mat3ra/code/dist/js/entity"; +import { taggableMixin } from "@mat3ra/code/dist/js/entity/mixins/TaggableMixin"; import { getUUID } from "@mat3ra/code/dist/js/utils"; import lodash from "lodash"; -import { mix } from "mixwith"; import { UNIT_STATUSES } from "../enums"; // eslint-disable-next-line max-len -export class BaseUnit extends mix( - NamedDefaultableRepetitionRuntimeItemsImportantSettingsContextAndRenderHashedInMemoryEntity, -).with(TaggableMixin) { +export class BaseUnit extends NamedDefaultableRepetitionRuntimeItemsImportantSettingsContextAndRenderHashedInMemoryEntity { constructor(config) { super({ ...config, @@ -92,3 +87,5 @@ export class BaseUnit extends mix( return super.clone(flowchartIDOverrideConfigAsExtraContext); } } + +taggableMixin(BaseUnit.prototype); diff --git a/src/units/builders/ExecutionUnitConfigBuilder.js b/src/units/builders/ExecutionUnitConfigBuilder.js index dfb1e04d..6bfccbae 100644 --- a/src/units/builders/ExecutionUnitConfigBuilder.js +++ b/src/units/builders/ExecutionUnitConfigBuilder.js @@ -1,15 +1,10 @@ -import { Application, Executable, Flavor } from "@exabyte-io/ade.js"; +/* eslint-disable class-methods-use-this */ +import ApplicationRegistry from "@exabyte-io/ade.js/dist/js/ApplicationRegistry"; import { UNIT_TYPES } from "../../enums"; import { UnitConfigBuilder } from "./UnitConfigBuilder"; export class ExecutionUnitConfigBuilder extends UnitConfigBuilder { - static Application = Application; - - static Executable = Executable; - - static Flavor = Flavor; - constructor(name, application, execName, flavorName, flowchartId) { super({ name, type: UNIT_TYPES.execution, flowchartId }); @@ -29,14 +24,8 @@ export class ExecutionUnitConfigBuilder extends UnitConfigBuilder { initialize(application, execName, flavorName) { this.application = application; - this.executable = this.constructor.Executable.create({ - name: execName, - application: this.application, - }); - this.flavor = this.constructor.Flavor.create({ - name: flavorName, - executable: this.executable, - }); + this.executable = this._createExecutable(this.application, execName); + this.flavor = this._createFlavor(this.executable, flavorName); } build() { @@ -47,4 +36,24 @@ export class ExecutionUnitConfigBuilder extends UnitConfigBuilder { flavor: this.flavor.toJSON(), }; } + + /** + * Creates an executable instance. This method is intended to be overridden in subclasses. + * @param {Application} application - The application object + * @param {string} execName - The name of the executable + * @returns {Executable} The created executable instance + */ + _createExecutable(application, execName) { + return ApplicationRegistry.getExecutableByName(application.name, execName); + } + + /** + * Creates a flavor instance. This method is intended to be overridden in subclasses. + * @param {Executable} executable - The executable object + * @param {string} flavorName - The name of the flavor + * @returns {Flavor} The created flavor instance + */ + _createFlavor(executable, flavorName) { + return ApplicationRegistry.getFlavorByName(executable, flavorName); + } } diff --git a/src/units/execution.js b/src/units/execution.js index 0211a021..9e8fa400 100644 --- a/src/units/execution.js +++ b/src/units/execution.js @@ -1,16 +1,16 @@ -import { Application, Template } from "@exabyte-io/ade.js"; -import { HashedInputArrayMixin } from "@mat3ra/code/dist/js/entity"; -import { removeTimestampableKeysFromConfig } from "@mat3ra/code/dist/js/utils"; -import { mix } from "mixwith"; +import { Template } from "@exabyte-io/ade.js"; +import ApplicationRegistry from "@exabyte-io/ade.js/dist/js/ApplicationRegistry"; +import { + calculateHashFromObject, + removeCommentsFromSourceCode, + removeEmptyLinesFromString, + removeTimestampableKeysFromConfig, +} from "@mat3ra/code/dist/js/utils"; import _ from "underscore"; import { BaseUnit } from "./base"; -export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { - static Application = Application; - - static Template = Template; - +export class ExecutionUnit extends BaseUnit { // keys to be omitted during toJSON static omitKeys = [ "job", @@ -22,18 +22,76 @@ export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { "hasRelaxation", ]; + /** + * @override this method to provide entities from other sources + */ _initApplication(config) { - this._application = this.constructor.Application.create(config.application); - this._executable = this._application.getExecutableByConfig(config.executable); - this._flavor = this._executable.getFlavorByConfig(config.flavor); + this._application = ApplicationRegistry.createApplication(config.application); + this._executable = ApplicationRegistry.getExecutableByConfig( + this._application.name, + config.executable, + ); + this._flavor = ApplicationRegistry.getFlavorByConfig(this._executable, config.flavor); this._templates = this._flavor ? this._flavor.inputAsTemplates : []; } + /** + * @override this method to provide default executable from other source + */ + _getDefaultExecutable() { + return ApplicationRegistry.getExecutableByName(this.application.name); + } + + /** + * @override this method to provide default flavor from other source + */ + _getDefaultFlavor() { + return ApplicationRegistry.getFlavorByName(this.executable.name); + } + + /** + * @override this method to provide custom templates + */ + _getTemplatesFromInput() { + return this.getInput().map((i) => new Template(i)); + } + + /** + * @override this method to provide custom input from other sources + */ + _getInput() { + return ( + this.input || + ApplicationRegistry.getInputAsRenderedTemplates( + this.flavor, + this.getCombinedContext(), + ) || + [] + ); + } + + /** + * @override this method to provide custom input as templates + */ + _getInputAsTemplates() { + return ApplicationRegistry.getInputAsTemplates(this.flavor); + } + _initRuntimeItems(keys, config) { this._initApplication(config); super._initRuntimeItems(keys); } + /* + * @summary expects an array with elements containing field [{content: "..."}] + */ + get hashFromArrayInputContent() { + const objectForHashing = this._getInput().map((i) => { + return removeEmptyLinesFromString(removeCommentsFromSourceCode(i.content)); + }); + return calculateHashFromObject(objectForHashing); + } + get name() { return this.prop("name", this.flavor.name); } @@ -54,29 +112,25 @@ export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { return this._templates; } - get templatesFromInput() { - return this.input.map((i) => new this.constructor.Template(i)); - } - setApplication(application, omitSettingExecutable = false) { this._application = application; this.setProp("application", application.toJSON()); if (!omitSettingExecutable) { - this.setExecutable(this.application.defaultExecutable); + this.setExecutable(this._getDefaultExecutable()); } } setExecutable(executable) { this._executable = executable; this.setProp("executable", executable.toJSON()); - this.setFlavor(this.executable.defaultFlavor); + this.setFlavor(this._getDefaultFlavor()); } setFlavor(flavor) { this._flavor = flavor; this.setRuntimeItemsToDefaultValues(); this.setProp("flavor", flavor.toJSON()); - this.setTemplates(this.flavor.inputAsTemplates); + this.setTemplates(this._getInputAsTemplates()); } setTemplates(templates) { @@ -126,11 +180,7 @@ export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { } get input() { - return ( - this.prop("input") || - this.flavor.getInputAsRenderedTemplates(this.getCombinedContext()) || - [] - ); + return this.prop("input"); } get renderingContext() { @@ -167,7 +217,7 @@ export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { const newRenderingContext = {}; const renderingContext = { ...this.context, ...context }; this.updateContext(renderingContext); // update in-memory context to properly render templates from input below - (fromTemplates ? this.templates : this.templatesFromInput).forEach((t) => { + (fromTemplates ? this.templates : this._getTemplatesFromInput()).forEach((t) => { newInput.push(t.getRenderedJSON(renderingContext)); Object.assign( newRenderingContext, @@ -204,7 +254,7 @@ export class ExecutionUnit extends mix(BaseUnit).with(HashedInputArrayMixin) { ...super.toJSON(), executable: this.executable.toJSON(), flavor: this.flavor.toJSON(), - input: this.input, + input: this._getInput(), // keys below are not propagated to the parent class on initialization of a new unit unless explicitly given name: this.name, // TODO: figure out the problem with storing context below diff --git a/tests/subworkflow.test.js b/tests/subworkflow.test.js index d9ed73ee..9378c605 100644 --- a/tests/subworkflow.test.js +++ b/tests/subworkflow.test.js @@ -1,4 +1,4 @@ -import { Application } from "@exabyte-io/ade.js"; +import ApplicationRegistry from "@exabyte-io/ade.js/dist/js/ApplicationRegistry"; import { expect } from "chai"; import { createSubworkflowByName } from "../src/subworkflows"; @@ -101,7 +101,7 @@ describe("subworkflows", () => { expect(subworkflow.units[0].application.version).to.be.equal("6.3"); expect(subworkflow.units[1].application?.version).to.be.equal(undefined); - const newApplication = Application.createFromNameVersionBuild({ + const newApplication = ApplicationRegistry.createApplication({ name: "espresso", version: "6.7.0", }); diff --git a/tests/unit.test.js b/tests/unit.test.js index 8c02cd3a..57ad7f3c 100644 --- a/tests/unit.test.js +++ b/tests/unit.test.js @@ -1,5 +1,9 @@ +import { Application } from "@exabyte-io/ade.js"; import { expect } from "chai"; +import { createUnit } from "../src/subworkflows/create"; +import { builders } from "../src/units/builders"; +import { UnitFactory } from "../src/units/factory"; import { createWorkflows } from "../src/workflows"; describe("units", () => { @@ -13,4 +17,34 @@ describe("units", () => { expect(exampleUnitClone).to.exist; expect(exampleUnit.flowchartId).to.not.equal(exampleUnitClone.flowchartId); }); + + it("can create execution unit", () => { + const unit = createUnit({ + config: { + type: "executionBuilder", + config: { + name: "test", + execName: "pw.x", + flavorName: "pw_scf", + flowchartId: "test", + }, + }, + application: new Application({ name: "espresso" }), + unitBuilders: builders, + unitFactoryCls: UnitFactory, + }); + + const expectedResults = [ + { name: "atomic_forces" }, + { name: "fermi_energy" }, + { name: "pressure" }, + { name: "stress_tensor" }, + { name: "total_energy" }, + { name: "total_energy_contributions" }, + { name: "total_force" }, + ]; + + expect(unit.flavor.results).to.deep.equal(expectedResults); + expect(unit.results).to.deep.equal(expectedResults); + }); }); diff --git a/tests/workflow.test.js b/tests/workflow.test.js index b89361b9..037ee38b 100644 --- a/tests/workflow.test.js +++ b/tests/workflow.test.js @@ -34,4 +34,14 @@ describe("workflow property", () => { // eslint-disable-next-line no-unused-expressions expect(mmWorkflow.isMultiMaterial).to.be.true; }); + + it("properties are not empty", () => { + const workflow = createWorkflow({ + appName: "espresso", + workflowData: allWorkflowData.workflows.espresso.total_energy, + }); + + // eslint-disable-next-line no-unused-expressions + expect(workflow.properties).to.be.an("array").that.is.not.empty; + }); });