diff --git a/README.md b/README.md index b62ada1..e365257 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,14 @@ since impact-client-js only exposes a small subset of the features available in For non-browser scripting purposes the best alternative is to use [impact-client-python](https://github.com/modelon-community/impact-client-python) if Python is an option. -## Installation +## Table of contents + +- [Installation](#installation) +- [Quick start](#quick-start) +- [Examples](#examples) +- [Development](#development) + +# Installation `npm install @modelon/impact-client-js` @@ -142,6 +149,10 @@ dotenv.config(); })(); ``` +## Examples + +See [examples](./examples). + ## Development Clone this repository then install the impact-client-js dependencies: diff --git a/examples/.eslintrc.js b/examples/.eslintrc.js new file mode 100644 index 0000000..9f1bb32 --- /dev/null +++ b/examples/.eslintrc.js @@ -0,0 +1,8 @@ +const rootEslintConfig = require('../.eslintrc') + +module.exports = { + ...rootEslintConfig, + rules: { + '@typescript-eslint/no-var-requires': 0, + }, +} diff --git a/examples/multi.js b/examples/multi.js new file mode 100644 index 0000000..e3dd7e3 --- /dev/null +++ b/examples/multi.js @@ -0,0 +1,65 @@ +// This script examplifies how to use the Range operator to create +// a multi-execution experiment. +// +// 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 + +const dotenv = require('dotenv') +const { + Analysis, + Client, + ExperimentDefinition, + Model, + Range, +} = require('@modelon/impact-client-js') + +// Load the .env file variables, install with: npm install dotenv +dotenv.config() +;(async () => { + 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 analysis = Analysis.from({ + type: 'dynamic', + }) + const model = Model.from({ + className: 'Modelica.Blocks.Examples.PID_Controller', + }) + + const experimentDefinition = ExperimentDefinition.from({ + analysis, + model, + modifiers: { + variables: { 'PI.k': new Range(10, 100, 3).toString() }, + }, + }) + + const experiment = await workspace.executeExperimentUntilDone({ + experimentDefinition, + timeoutMs: 60 * 1000, + }) + + const cases = await experiment.getCases() + + for (const simCase of cases) { + const timeItems = await simCase.getTrajectories(['time']) + const timeEndValue = timeItems[0].trajectory.pop() + const inertiaItems = await simCase.getTrajectories(['inertia1.phi']) + const inertiaEndValue = inertiaItems[0].trajectory.pop() + const caseInput = await simCase.getInput() + console.log( + `End value for case with PI.k: ${caseInput.parametrization['PI.k']} at time ${timeEndValue}: ${inertiaEndValue}` + ) + } + + await client.deleteWorkspace(WorkspaceName) +})() diff --git a/src/api.ts b/src/api.ts index 85e45c6..a9c3e33 100644 --- a/src/api.ts +++ b/src/api.ts @@ -12,6 +12,7 @@ import Workspace from './workspace' import { Case, CaseId, + CaseInput, CaseTrajectories, CustomFunction, CustomFunctionOptions, @@ -637,6 +638,28 @@ class Api { .catch((e) => reject(toApiError(e))) }) + getCaseInput = ({ + caseId, + experimentId, + workspaceId, + }: { + caseId: CaseId + experimentId: ExperimentId + workspaceId: WorkspaceId + }): Promise => + new Promise((resolve, reject) => { + this.ensureImpactToken() + .then(() => { + this.axios + .get( + `${this.baseUrl}${this.jhUserPath}impact/api/workspaces/${workspaceId}/experiments/${experimentId}/cases/${caseId}` + ) + .then((res) => resolve(res.data.input)) + .catch((e) => reject(toApiError(e))) + }) + .catch((e) => reject(toApiError(e))) + }) + getCaseLog = ({ caseId, experimentId, diff --git a/src/case.ts b/src/case.ts index f43d7e4..3f2a6b6 100644 --- a/src/case.ts +++ b/src/case.ts @@ -34,6 +34,13 @@ class Case { this.workspaceId = workspaceId } + getInput = () => + this.api.getCaseInput({ + caseId: this.id, + experimentId: this.experimentId, + workspaceId: this.workspaceId, + }) + getLog = () => this.api.getCaseLog({ caseId: this.id, diff --git a/src/index.ts b/src/index.ts index 38a7dc0..6dc0e57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { WorkspaceDefinition, } from './types/index.d' import Model from './model' +import Range from './range' import Workspace from './workspace' export { @@ -61,6 +62,7 @@ export { ModelicaExperimentExtensions, ModelicaExperimentModifiers, ModelicaModel, + Range, ServerNotStarted, UnknownApiError, Workspace, diff --git a/src/range.ts b/src/range.ts new file mode 100644 index 0000000..f9d1c82 --- /dev/null +++ b/src/range.ts @@ -0,0 +1,15 @@ +class Range { + private start: number + private end: number + private step: number + + constructor(start: number, end: number, step: number) { + this.start = start + this.end = end + this.step = step + } + toString(): string { + return `range(${this.start},${this.end},${this.step})` + } +} +export default Range diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 5f56186..1f1f511 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -36,6 +36,9 @@ export type ModelicaExperimentAnalysis = components['schemas']['Analysis'] export type ModelicaExperimentModifiers = components['schemas']['Modifiers'] +export type CaseInput = + operations['getAllCaseInfo']['responses']['200']['content']['application/json']['data']['items'][0]['input'] + export type CaseTrajectories = operations['getTrajectories']['responses']['200']['content']['application/vnd.impact.trajectories.v2+json']['data']['items']