diff --git a/package-lock.json b/package-lock.json index faea4bb..7d6b2b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,12 +27,12 @@ "underscore.string": "^3.3.4" }, "devDependencies": { - "@exabyte-io/ade.js": "git+https://github.com/Exabyte-io/ade.js.git#bbb62dc12f4ae7c4ba32edc60e459b8bbbcd232d", + "@exabyte-io/ade.js": "git+https://github.com/Exabyte-io/ade.js.git#51864363d0d9525a2481b733068cb00396806370", "@exabyte-io/application-flavors.js": "2025.5.10-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": "git+https://github.com/Exabyte-io/code.git#3fbf30e5318c9f1ab7a3a5b967fa57d2b70cad3e", + "@mat3ra/code": "git+https://github.com/Exabyte-io/code.git#72a4dd05240da180f09fcb04db9f469aea4077af", "@mat3ra/esse": "2025.4.26-0", "@mat3ra/made": "git+https://github.com/Exabyte-io/made.git#84807b83b15dfd632fae9495be301aa7df503b11", "chai": "^4.3.4", @@ -2383,8 +2383,8 @@ }, "node_modules/@exabyte-io/ade.js": { "version": "0.0.0", - "resolved": "git+ssh://git@github.com/Exabyte-io/ade.js.git#bbb62dc12f4ae7c4ba32edc60e459b8bbbcd232d", - "integrity": "sha512-uhFBtkZN1rXnUwbhD7ntzDxsnuYQdacmXbZxyy6cjV4vctRmwkDUIrZ3hn7FapNaBxLi8xHz0xMYycBxTpYF8g==", + "resolved": "git+ssh://git@github.com/Exabyte-io/ade.js.git#51864363d0d9525a2481b733068cb00396806370", + "integrity": "sha512-V5aYHiKdltBocyMeiPy35sTMk0h6KKpPiexZwONYlhfriOOFLh5xIaxaQqfUbsTgRBlx84MCV7nJiQJ7jMobwA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2811,8 +2811,8 @@ }, "node_modules/@mat3ra/code": { "version": "0.0.0", - "resolved": "git+ssh://git@github.com/Exabyte-io/code.git#3fbf30e5318c9f1ab7a3a5b967fa57d2b70cad3e", - "integrity": "sha512-w/v9+NRIZkRt6Ov5jBVowGmxnuAv1JrXMOderAZd9kVqTEvDMcPpImd1NV1VQTHG8xy7Uq2piRS0a15SyyVh6Q==", + "resolved": "git+ssh://git@github.com/Exabyte-io/code.git#72a4dd05240da180f09fcb04db9f469aea4077af", + "integrity": "sha512-hFH6YKXzLlVyc2B936NEnbyaptfXHbdNL01uqB741op4GdZTpy6eBvxA43BKt8ej1gRIzoIdKsxEZhZnTJUe7Q==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index b735c48..11ef69b 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,12 @@ "underscore.string": "^3.3.4" }, "devDependencies": { - "@exabyte-io/ade.js": "git+https://github.com/Exabyte-io/ade.js.git#bbb62dc12f4ae7c4ba32edc60e459b8bbbcd232d", + "@exabyte-io/ade.js": "git+https://github.com/Exabyte-io/ade.js.git#51864363d0d9525a2481b733068cb00396806370", "@exabyte-io/application-flavors.js": "2025.5.10-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": "git+https://github.com/Exabyte-io/code.git#3fbf30e5318c9f1ab7a3a5b967fa57d2b70cad3e", + "@mat3ra/code": "git+https://github.com/Exabyte-io/code.git#72a4dd05240da180f09fcb04db9f469aea4077af", "@mat3ra/esse": "2025.4.26-0", "@mat3ra/made": "git+https://github.com/Exabyte-io/made.git#84807b83b15dfd632fae9495be301aa7df503b11", "chai": "^4.3.4", diff --git a/src/subworkflows/create.js b/src/subworkflows/create.js index cb8f436..4688373 100644 --- a/src/subworkflows/create.js +++ b/src/subworkflows/create.js @@ -1,4 +1,4 @@ -import { Application } from "@exabyte-io/ade.js"; +import AdeFactoryDefault from "@exabyte-io/ade.js/dist/js/AdeFactory"; 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 AdeFactory * @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, AdeFactory }) { const { application: appConfig, model: modelConfig, method: methodConfig } = subworkflowData; - const application = createApplication({ config: appConfig, applicationCls }); + const application = AdeFactory.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, + AdeFactory = AdeFactoryDefault, modelFactoryCls = ModelFactory, methodFactoryCls = MethodFactory, subworkflowCls = Subworkflow, @@ -152,7 +141,7 @@ function createSubworkflow({ }) { const { application, model, method, setSearchText } = createTopLevel({ subworkflowData, - applicationCls, + AdeFactory, modelFactoryCls, methodFactoryCls, }); diff --git a/src/units/base.js b/src/units/base.js index 98b0dfc..c97690b 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 dfb1e04..587feb5 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 AdeFactory from "@exabyte-io/ade.js/dist/js/AdeFactory"; 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 AdeFactory.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 AdeFactory.getFlavorByName(executable, flavorName); + } } diff --git a/src/units/execution.js b/src/units/execution.js index 0211a02..3d2a3b9 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 AdeFactory from "@exabyte-io/ade.js/dist/js/AdeFactory"; +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,73 @@ 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 = AdeFactory.createApplication(config.application); + this._executable = AdeFactory.getExecutableByConfig( + this._application.name, + config.executable, + ); + this._flavor = AdeFactory.getFlavorByConfig(this._executable, config.flavor); this._templates = this._flavor ? this._flavor.inputAsTemplates : []; } + /** + * @override this method to provide default executable from other source + */ + _getDefaultExecutable() { + return AdeFactory.getExecutableByName(this.application.name); + } + + /** + * @override this method to provide default flavor from other source + */ + _getDefaultFlavor() { + return AdeFactory.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 || + AdeFactory.getInputAsRenderedTemplates(this.flavor, this.getCombinedContext()) || + [] + ); + } + + /** + * @override this method to provide custom input as templates + */ + _getInputAsTemplates() { + return AdeFactory.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 +109,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 +177,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 +214,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 +251,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 d9ed73e..2bfb6f9 100644 --- a/tests/subworkflow.test.js +++ b/tests/subworkflow.test.js @@ -1,4 +1,4 @@ -import { Application } from "@exabyte-io/ade.js"; +import AdeFactory from "@exabyte-io/ade.js/dist/js/AdeFactory"; 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 = AdeFactory.createApplication({ name: "espresso", version: "6.7.0", }); diff --git a/tests/unit.test.js b/tests/unit.test.js index 8c02cd3..57ad7f3 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 b89361b..037ee38 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; + }); });