Skip to content

Commit

Permalink
feat: new classes for model executable/description
Browse files Browse the repository at this point in the history
Added classes:

- ModelDescription
- ModelExecutable
- DefaultExperiment

See examples/modelDescription.mjs for example usage.
  • Loading branch information
x-oflisback committed Sep 14, 2023
1 parent 135c394 commit 78ba58c
Show file tree
Hide file tree
Showing 15 changed files with 1,435 additions and 26 deletions.
48 changes: 48 additions & 0 deletions examples/modelDescription.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This script examplifies how to get model description units and variables.
//
// Make sure to have installed dependencies and have the required environment variables
// available, as described in the Quick start example:
//
// https://github.com/modelon-community/impact-client-js#quick-start
//
// Then run the example with: node modelDescription.mjs

import { Analysis, Client, ExperimentDefinition, Model } from '../dist/index.js'
import dotenv from 'dotenv'

// Load the .env file variables, install with: npm install dotenv
dotenv.config({ path: '../.env' })

const client = Client.fromImpactApiKey({
impactApiKey: process.env.MODELON_IMPACT_CLIENT_API_KEY,
jupyterHubToken: process.env.JUPYTERHUB_API_TOKEN,
serverAddress: process.env.MODELON_IMPACT_SERVER,
})

const WorkspaceName = 'test'

const workspace = await client.createWorkspace({
name: WorkspaceName,
})

const experimentDefinition = ExperimentDefinition.from({
analysis: Analysis.from(Analysis.DefaultAnalysis),
model: Model.from({ className: 'Modelica.Blocks.Examples.PID_Controller' }),
})

const experiment = await workspace.executeExperimentUntilDone({
caseIds: ['case_1'],
experimentDefinition,
})

const cases = await experiment.getCases()
const modelExecutable = await cases[0].getModelExecutable()
const modelDescription = await modelExecutable.getModelDescription()

const variables = modelDescription.getVariables()
console.log(variables[0])

const units = modelDescription.getUnits()
console.log(units[0])

await client.deleteWorkspace(WorkspaceName)
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"axios": "^0.27.2",
"axios-cookiejar-support": "^4.0.3",
"fast-xml-parser": "^4.2.7",
"tough-cookie": "^4.0.0"
},
"devDependencies": {
Expand Down
58 changes: 58 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import {
ExperimentItem,
ExperimentTrajectories,
ExperimentVariables,
FmuId,
LocalProjectProtocol,
ModelExecutableInfo,
ModelicaExperimentDefinition,
ProjectId,
WorkspaceProtocol,
Expand Down Expand Up @@ -682,6 +684,26 @@ class Api {
.catch((e) => reject(toApiError(e)))
})

getModelDescription = ({
fmuId,
workspaceId,
}: {
fmuId: FmuId
workspaceId: WorkspaceId
}): Promise<string> =>
new Promise((resolve, reject) => {
this.ensureImpactToken()
.then(() => {
this.axios
.get(
`${this.baseUrl}${this.jhUserPath}impact/api/workspaces/${workspaceId}/model-executables/${fmuId}/model-description`
)
.then((res) => resolve(res.data))
.catch((e) => reject(toApiError(e)))
})
.catch((e) => reject(toApiError(e)))
})

getCustomFunctionOptions = ({
customFunction,
workspaceId,
Expand Down Expand Up @@ -737,6 +759,42 @@ class Api {
this.configureAxios()
}

getModelExecutableInfo = ({
fmuId,
workspaceId,
}: {
fmuId: FmuId
workspaceId: WorkspaceId
}): Promise<ModelExecutableInfo> =>
new Promise((resolve, reject) => {
this.ensureImpactToken()
.then(() => {
this.axios
.get(
`${this.baseUrl}${this.jhUserPath}impact/api/workspaces/${workspaceId}/model-executables/${fmuId}`
)
.then((response) => resolve(response.data))
.catch((e) => reject(toApiError(e)))
})
.catch((e) => reject(toApiError(e)))
})

getModelExecutableInfos = (
workspaceId: WorkspaceId
): Promise<ModelExecutableInfo[]> =>
new Promise((resolve, reject) => {
this.ensureImpactToken()
.then(() => {
this.axios
.get(
`${this.baseUrl}${this.jhUserPath}impact/api/workspaces/${workspaceId}/model-executables`
)
.then((response) => resolve(response.data?.data?.items))
.catch((e) => reject(toApiError(e)))
})
.catch((e) => reject(toApiError(e)))
})

delete = (path: string) =>
new Promise((resolve, reject) => {
this.ensureImpactToken()
Expand Down
18 changes: 18 additions & 0 deletions src/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,37 @@ import {
CaseId,
CaseTrajectories,
ExperimentId,
FmuId,
WorkspaceId,
} from './types'
import Api from './api'
import ModelExecutable from './model-executable'

class Case {
private api: Api
private experimentId: ExperimentId
private fmuId?: FmuId
id: CaseId
runInfo: CaseRunInfo
private workspaceId: WorkspaceId

constructor({
api,
experimentId,
fmuId,
id,
runInfo,
workspaceId,
}: {
api: Api
id: CaseId
fmuId?: FmuId
experimentId: ExperimentId
runInfo: CaseRunInfo
workspaceId: WorkspaceId
}) {
this.api = api
this.fmuId = fmuId
this.id = id
this.runInfo = runInfo
this.experimentId = experimentId
Expand All @@ -48,6 +54,18 @@ class Case {
workspaceId: this.workspaceId,
})

getModelExecutable = async () => {
if (!this.fmuId) {
return null
}

return ModelExecutable.from({
api: this.api,
fmuId: this.fmuId,
workspaceId: this.workspaceId,
})
}

getTrajectories = (variableNames: string[]): Promise<CaseTrajectories> =>
this.api.getCaseTrajectories({
caseId: this.id,
Expand Down
25 changes: 25 additions & 0 deletions src/default-experiment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class DefaultExperiment {
startTime
stepSize
stopTime
tolerance

constructor({
startTime,
stepSize,
stopTime,
tolerance,
}: {
startTime?: number
stepSize?: number
stopTime?: number
tolerance?: number
}) {
this.startTime = startTime
this.stepSize = stepSize
this.stopTime = stopTime
this.tolerance = tolerance
}
}

export default DefaultExperiment
1 change: 1 addition & 0 deletions src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Experiment {
new Case({
api: this.api,
experimentId: this.id,
fmuId: caseResponse.input.fmu_id,
id: caseResponse.id || i.toString(),
runInfo: caseResponse.run_info,
workspaceId: this.workspaceId,
Expand Down
61 changes: 61 additions & 0 deletions src/model-description.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import DefaultExperiment from './default-experiment'
import { Unit, Variable } from './types'

type ModelDescriptionData = {
DefaultExperiment: Record<string, string>
ModelVariables: {
ScalarVariable: Variable[]
}
UnitDefinitions: { Unit: Unit[] }
modelName: string
}

class ModelDescription {
private data: ModelDescriptionData

constructor(data: ModelDescriptionData) {
this.data = data
}

getDefaultExperiment() {
const defaultExperiment = this.data.DefaultExperiment

const toFloatOrUndefined = (key: string, obj: Record<string, string>) =>
obj[key] ? parseFloat(obj[key]) : undefined

const parameters: ConstructorParameters<typeof DefaultExperiment>[0] =
{}

const defaultExperimentKeys = [
'startTime',
'stepSize',
'stopTime',
'tolerance',
] as const

type DefaultExperimentKey = typeof defaultExperimentKeys[number]

defaultExperimentKeys.forEach((key) => {
parameters[key as DefaultExperimentKey] = toFloatOrUndefined(
key,
defaultExperiment
)
})

return new DefaultExperiment(parameters)
}

getModelName() {
return this.data.modelName
}

getUnits() {
return this.data.UnitDefinitions.Unit
}

getVariables(): Variable[] {
return this.data.ModelVariables.ScalarVariable
}
}

export default ModelDescription
Loading

0 comments on commit 78ba58c

Please sign in to comment.