Challenge Overview
GE wants Topcoder community create Predix applications that emulate various devices interoperating with Predix services over the network. This challenge is a part of a series of Predix contests with the total prize pool over $10,000. We hope you will take time to register for Predix, read about the services they provide, and configure your environment. Other challenges in this series may use different technology stack and approach very different problems, but all of them will utilize Predix account and different services provided by the platform.
In this challenge you will create a steam locomotive process model. It will be quite simple, so don’t be scared by the size of this specs.
Setup
If you have not registered already, go to http://predix.topcoder.com/ and join Topcoder Predix Community. Fill out the registration form and confirm your email address. Once completed, you will receive community welcome email with a link to register your Predix developer account.During registration of the Predix dev account, state Topcoder as your company. Be aware that approval of the Predix account can take a few days at the Predix side. To smoother the process, please message in the challenge forum your Topcoder Handle / First and Last Names / Email that you have used for registration of Predix account. We will pass these data to GE Predix team to speed-up the process.
Eligibility: As of now, residents of the following countries are not allowed to register in Predix, hence they won’t be able to participate in this challenge: China, Cuba, Iran, North Korea, Russia, Sudan, Syria.
Previous Challenge Relevant
The previous challenge was related to this one. We have developed a simple C++ emulator of an abstract sensor, which interacted with Predix services, using:- Time Series Service to record sensor measurements;
- Asset Service to store alerts / error messages generated by the sensor;
- User Account and Authentication (UAA) to authenticate the sensor into Predix platform.
These are the same Predix services you will need to connect with in this challenge, so the winning submission from that challenge is provided in the forum for your reference.
This Challenge
You will implement a similar solution in NodeJS, using a model of a generic device, rather than the simple sensor of the previous challenge. You will use it to simulate a steam locomotive (do not worry, our model will be a simple one, to make a reasonably nice demo / toy, rather than engineering quality model, at least for now :)1. Generic Simulator. First of all, create a generic device simulator, which will be used to model the locomotive in this challenge, and will be easily reusable for modeling other industrial assets. Here is the idea:
1.1. We provide the simulator with an object (below) which will describe:
1.1.1. A set of variables which describe the state of the system.
1.1.2. Initial values of those variables.
1.1.3. Transition functions, which, given the state of the system at the moment t, calculate the state at the moment t + dt.
1.1.4. Threshold values of the state variables, which should be messaged to the asset service, if reached.
1.2. The simulator will repeatedly iterate through and evaluate transition functions, evolving the system state over time.
1.3. After each simulation step (1.2) the system will send the state variable values to Predix Time Series. Think of these as sensor readings. It will also send alerts to Predix Asset when any variables reach their thresholds. These two operations should be handled by two separate functions placed in an array of functions to be executed after time advances, in the property onStateChange (see below). These functions should return Promises. Your code that executes them should wait for Promise.all to resolve before advancing to the next iteration.
Here is the example (with steam locomotive in mind), which shows that it is quite simple:
{ /* Specifies both the set of variables describing the state of * the system, and their initial values. */ state: { distance: 0.0, /* meters */ fuelMassBurning: 0.0, /* kg */ fuelMassInTender: 9000.0, /* kg */ fuelMassInFireChamber: 0.0, /* kg */ locomotiveOwnMass: 500000.0, /* kg */ pressure: 0.0, /* bar */ speed: 0.0, /* meters per second */ time: 0.0, /* seconds */ }, /* Transition functions define the simulated process. */ processes: [{ /* Emulates the fireman. In our simple model he just moves * the fuel from tender into the fire chamber. */ name: ‘Fireman’, transferFunction: (state) => { if (state.fuelMassInTender < 0.0 || state.fuelMassInFireChamber > 10.0) { return state; } return { ...state, fuelMassInTender: state.fuelMassInTender - 1.0, fuelMassInFireChamber: state.fuelMassInFireChamber + 1.0, }; }, }, { /* An oversimplified model of fire chamber and boiler: * - Fuel added into fire chamber slowly becomes burning; * - Burning fuel is consumed (disappears) slowly; * - Pressure in boiler is just proportional to the amount * of burning fuel (so we don’t care about the boiler model * for now). */ name: ‘Fire Chamber’, transferFunction: (state) => { const res = { ...state }; if (res.fuelMassBurning < res.fuelMassInFireChamber) { res.fuelMassBurning = Math.min(res.fuelMassInFireChamber, 1.0 + res.fuelMassBurning); } res.pressure = 2 * res.fuelMassBurning; res.fuelMassBurning = Math.max(0, res.fuelMassBurning - 0.1); res.fuelMassInFireChamber = Math.max(0, res.fuelMassInFireChamber - 0.1); return res; }, }, { /* The rest of the model. Assumes that locomotive acceleration * is proportional to the pressure in boiler, updates its * position, speed, world time. */ name: ‘Movement’, transferFunction: (state) => { const dt = 0.01; /* Simulation step. */ /* a is the acceleration, calculated as the difference * between the force created by engine and any work lost * due to friction, whatever. The actual coefficients * should be fitted so that the results make sense. */ let a = 3.0 * state.pressure / (state.locomotiveOwnMass + state.fuelMassInTender + state.fuelMassInFireChamber) - 2.0; if (a < 0.0) a = 0.0; return { ...state, speed: state.speed + dt * a, distance: state.distance + dt * state.speed, time: state.time + dt, }; }, }], /* Configuration of the alerts to be sent to the assets * service. */ onStateChange: [ state => (state.pressure > 15.0 ? toAsset(state.pressure) : undefined), state => toTimeSeries(state), ], }
Given this object the iterator will:
1. Evaluate the transfer functions one-by-one, updating the state of the system.
2. Evaluate the function in the onStateChange array. There will be two:
2.a. One will send the resulting state into the Timeseries service.
2.b. The second will POST a JSON object to the Asset service alerts collection.
3. Repeat.
2. Locomotive Simulator. For demo purposes, you will configure your generic simulator to perform the simulation described by the example above. Feel free to modify coefficients / formulas so that the results of the simulation make sense (we don’t want to see the locomotive going faster than a brand-new Ferrari car :)
Please follow the standard best practices in your code! Setup ESLint (AirBnB rules are fine), comment the code appropriately, don’t use magic numbers inside the code, like it is done in the example (define them as constants before the use), etc.
Last, but not least: We welcome your creativity in this challenge! Should you have an idea about any enhancements of the model, or some handy corrections to the specs / scope, feel free to propose / discuss in the challenge forum. We might include your ideas into the scope as an additional features, provided they do not expand the scope significantly.
We plan on using the winning submission from this challenge in future challenges, extending functionality, adding visualizations, and other Predix functionality as well.