Programming best practice - decision based on many input variables

Afternoon folks, bit of a different conundrum today...

I'm trying to work out the best / easiest / most efficient way to build a "condition list" in PBasic...

My use-case is interlocking for a model railway, with the sequence of events happening as follows:
  1. "requested" command received (eg. set route 1)
  2. conditions tested
  3. command accepted or rejected
The conditions would be things such as "is the track clear? Are the points set?". These will all be stored in variables, and be true/false statements.
For the command to be "accepted", all the conditions would have to be true.

Bearing in mind that each possible command would have different conditions, what would be the best way to implement this in code?
The most simple method would obviously be to have an if-AND-then statement, but that's ridiculously time-consuming... surely there's a better way?

cheers
 

Buzby

Senior Member
I tend to use a list of individual if-then-return statements, each of which resets the single 'ConditionsMet' variable,

e.g.

gosub CheckRoute1
if ConditionsMet = True then
StartRoute1
endif


: Subroutine CheckRoute1
ConditionsMet = True
if firstcondition = False then ConditionsMet = False : Return : endif
if secondcondition = False then ConditionsMet = False : Return : endif
if thirdcondition = False then ConditionsMet = False : Return : endif
Return ' All conditions met



There other cleverer ways of doing this kind of work, but they need more forethought, and are more difficult to debug.

Keep it simple !.


If you want feedback as to which condition is not met then it is easy to add, just make 'ConditionsMet' an integer variable, and the tests becomes like

gosub CheckRoute1
if ConditionsMet = 0 then
StartRoute1
else
display_fault(ConditionsMet)
endif


: Subroutine CheckRoute1
ConditionsMet = 0
if firstcondition = False then ConditionsMet = 1 : Return : endif
if secondcondition = False then ConditionsMet = 2 : Return : endif
if thirdcondition = False then ConditionsMet = 3 : Return : endif
Return ' All conditions met returns 0
 
Last edited:

hippy

Technical Support
Staff member
Probably the first thing to do is to list what all these commands could be, for example ...

"Set Route <n>" where <n> is 1 to 10

And then list what all the conditions could be, for example ...

"Points <n> Open"
"Signal <n> Green"

Then give some idea of how many conditions there are likely to be for a particular request, and maybe provide a non-trivial example. Are they all AND conditions or can there be OR conditions, even bracketed sub expressions?

There are probably three ways to go about it. First to have a big SELECT-CASE for each request, with checks for each condition with a result returned.

Second create some data representation which is the same as above but more compact, which can then be 'executed' to give a result.

And the third being some sort of mid-way between the two.

It's a bit hard to suggest what is best, or any particular implementation, without knowing the scale of the problem to be solved. It's usually not the best idea to try and implement something before knowing exactly what it is one has to implement.

If you have a list of requests and conditions it would be great if you could post that. That would probably be the best approach, providing those two lists I started with, giving an idea of the scale and complexity. It's what you are going to need anyway to implement anything, and to verify the code's correctness.

I would be tempted to approach this from an angle of having that list defined in a text file on your PC, and automatically generate the actual PICAXE code from that file. That allows you to easily change things and fix bugs which will inevitably occur by simply editing an easy to understand file, and quickly producing new PICAXE code.

The main trick is making what's required simple, regardless of how complicated the PICAXE code may be.

Even if not liking that auto-generation idea it is probably the best starting point. Turning that into code might just be a simple case of translating 'English' into something a PICAXE can understand, and macros can help with that. You might even be able to write your specification as macros, no generation necessary -
Code:
SetRoute1:
  PointsOpen(1)
  SignalGreen(1)
  Return
That's complete code, fully working. Provided you've got the macros defined.

This type of 'top-down' approach is great for developing programs. The program is already done as soon as you have your top level complete, even if code required to turn it into a reality isn't written yet.

And the best thing is that, once you get PointsOpen(1) working, it's pretty easy to get any PointsOpen(n) code working.

That's what I'd suggest anyway.
 

westaust55

Moderator
Based on the limited information,
Can you consider bit variables.
For a given route and
If all must be true (=1) then check the byte or word variable than this bits are part thereof. A single test that all bits are 1 could then be conducted.
Eg byte = 32 if 5 bits involved, = 255 if 8 bits involved
Or for more than 8 then for example
Word = 511 if 9 bits and 2047 if 11 bits involved.
 
Hey guys, cheers for the replies... only just now got around to typing up a reply :)

In terms of scale, I'm currently looking at between 50-100 possible routes in the system, each with its own scale in terms of checks required.
As an example, a route from E499 to E503 (real signal numbers, not indicative of scale :D) involves the following:

15 conflicting routes
8 track circuits

All of these must be clear in order for the 3 double-ended points to change, and the route to be cleared.

Unfortunately, that's likely to be one of the "mid-sized" routes... its in the "scenic section", so everything is relatively spread out. Once I get into the storage yard, I'm going to end up with many more track circuits and conflicting routes.

Just within the scenic section, there are a total of 18 possible routes which can be set, and outside the scenic section this will increase massively.

Unfortunately, as I don't currently have a full list of track circuits and routes, I can't develop a full interlocking table, although this is coming :)

Based on the limited information,
Can you consider bit variables.
For a given route and
If all must be true (=1) then check the byte or word variable than this bits are part thereof. A single test that all bits are 1 could then be conducted.
Eg byte = 32 if 5 bits involved, = 255 if 8 bits involved
Or for more than 8 then for example
Word = 511 if 9 bits and 2047 if 11 bits involved.
I do like that idea - that makes a lot of sense... Perhaps I would alter it slightly, where all the "states" are stored in such variables, and I'd just end up with a large number of them. Then when going through the request, I could simply mask the bits which are of no interest to me, then compare against a "pre-determined" value

For example, if I needed routes 1, 4 and 5 clear:

Code:
b0 = b1 & %00011001        'b0 = "working" variable, b1 = "status" variable. Mask all but bits 0, 3 & 4.

if b0 = 0 then            'b0 is masked to only contain the bits of interest. Since all need to be clear, the "required" result is 0.
    'success
else
    'fail
end if
It obviously gets a little more complicated with points, when I sometimes need them "set" and sometimes "clear", but then I just do a little maths in advance and set to "required" result to something other than 0, dependent on the required settings.
 
In fact, thinking about it even more (dangerous, I know), I could eliminate the "conflicting routes" check by doing the following:

When a route is set, it "reserves" itself by setting the bits for all the track circuits on the route. (only happens in the interlocking, not on the display). Then, when a conflicting route is checked, it would be "blocked" by these "occupied" track circuits, preventing it being set.

As the train passes over the route, it sequentially clears the track circuits as it leaves them, allowing you to set a route behind the train before it's finished going through the full route.

Two advantages - only one check required during runtime, and more efficient operation, as a new route can be set across the "old" route before the "old" one has been "completed"...
 

hippy

Technical Support
Staff member
I still think you are rushing into an implementation without have clearly defined the problem which needs to be solved.

Trying to decide whether bit variables can be used and how the code can be optimised is entirely premature.

One needs to take a step back and visualise how it will work, what the mechanism will be without worrying about the implementation. Get that visualisation completely proven and debugged, any flaws in it resolved and ironed-out, then worry about the implementation.

Code:
           A      B      C          D                             
o--[2S56]--o------o------o----------o-----o E
                   \
                    \    F          G        
                     `---o--[1X01]--o-----o H
                          \          \
                           \          `---o I
                            \     
                             `------------o J
That's 10 sections, 3 points, and 4 routes -
Code:
Route 1 : A -> E 
Route 2 : A -> H 
Route 3 : A -> I 
Route 4 : A -> J
And for each route we can specify rules which determine the conditions under which a route can be completed -
Code:
Route 1 : A -> E

  Clear A-B   Track between A and B must be clear
  Point B-C   Points at B must be able to point to C 
  Clear B-C   Track between B and C must be clear
  Clear C-D   Track between C and D must be clear
  Clear D-E   Track between D and E must be clear
Code:
Route 2 : A -> H

  Clear A-B   Track between A and B must be clear
  Point B-F   Points at B must be able to point to F 
  Clear B-F   Track between B and F must be clear
  Point F-G   Points at F must be able to point to G 
  Clear F-G   Track between F and G must be clear
  Point G-H   Points at G must be able to point to H
  Clear G-H   Track between G and H must be clear
And similar for routes 3 and 4.

So to tell if one can complete a particular route, all one does is work down the list until one passes all conditions or doesn't.

But one is not likely to want to set a route based on completion, just whether one can progress. For example A-H and A-I are blocked by HMQ occupying F-G, but we can progress A-B with F-G likely cleared by the time we reach B, so either B-H or B-I will then be clear, both clear by the time we reach F.

So all routes could be considered completable as it is in the above diagram.

State isn't a simple yes or no, it's how much can be completed; none, some or all. That relies on back-propagation as well as looking forward.

One may consider B-F clear with points at F set to F-J, but one might not while HMQ is occupying F-G for security and safety reasons. That's the back-propagation effect, because it depends on who is occupying F-G. It's also necessary if there's any reverse running or dual direction track.

Whether one even needs to check if a route can be completed before scheduling it is perhaps even a debatable point. In an ideal world one would set it and let the system take care of progression, signalling allowing a train to proceed or not.

But it will likely be different for a replica railway than it is on real tracks, because 'signals are just for show' don't have any effect on a train.

In both cases though one needs to define what it is one is wanting and how that works with what one has before embarking on a solution and implementation for that.
 

Buzby

Senior Member
When a route is set, it "reserves" itself by setting the bits for all the track circuits on the route. ...
This is a very similar technique to one I have used in factories for routing product through various pipes and valves. There is a group of bits for each item ( pump, valve, tank, etc. ), with each bit representing 'Available' ,'Running', Reserved', etc.

This is an easy and flexible method when used in a high level language which handles structured variables, but will be a bit more of a challenge in PICAXE BASIC. I envisage tables representing routes, which consist of pointers to tables of tracks and points. Two levels of indirection !.

You have not said if your proposed controller contains some kind of display. A simple serial LCD/OLED screen would make diagnosis during commissioning ( and running ) so much easier. It could be as simple as text to display the route requirements and states.

This system is a lot bigger than I first thought. If you start coding one way now, you could eventually hit a brick wall because of something you didn't consider earlier.

As hippy said, you really need to define what the system needs to do, ( ie write a Functional Requirements document ), before deciding how to do it.

Cheers,

Buzby
 

Dartmoor

Member
As usual, I strongly suggest you follow Hippy's advice.
With 50-100 routes you will not do this on a Picaxe as you will run out of I/O even if you can process the data. As a minimum you will need a number of 40X2's linked by serial data.
As already stated, you do need to work out what is required before working out how to achieve it.
The logic can be straight boolean or ladder logic, although if . . . then statements will obviously work too.
The simplest (but not authentic/prototypical) way is operate the points to positions required, then operate a switch to clear the signal. It is also possible to use a direction switch for each section as well as/instead of the signal switch. This avoids the need for routes but can introduce other problems depending on how you want to run the trains?
I guess you also want to automate the system at some stage?
It is all quite possible.
Can I suggest you look at joining MERG (Model Electronic Railway Group) as someone has probably tried this before?
 

hippy

Technical Support
Staff member
With 50-100 routes you will not do this on a Picaxe as you will run out of I/O even if you can process the data. As a minimum you will need a number of 40X2's linked by serial data.
There will be a reasonable amount of input; sections occupied and point setting signals. It should be possible to bring them all to one master PICAXE using slave PICAXE or other signal multiplexors.

There will also likely be quite a lot of routing rules which may be too much to have as PICAXE code or stored in internal Eeprom, but it could be stored in external I2C Eeprom.

Though I have said 'don't do it until what's needed is figured out', it would be possible to implement a half-way house which can be used to test the theory and check if routings are completely available. The I/O requirements and routing rules are likely to be the same no matter what the 'setting a route' algorithm becomes.

That takes us to how to specify what we have as tracking, sections, points and what rules there are.

The best solution is usually the 'least amount of effort' solution, and that usually means least amount of data entry. That also means data which can easily be changed. In that respect I took my original simplistic tracking -
Code:
           A      B      C          D                             
o--[2S56]--o------o------o----------o-----o E
                   \
                    \    F          G        
                     `---o--[1X01]--o-----o H
                          \          \
                           \          `---o I
                            \     
                             `------------o J
And coded that as a simple, 'from-to-to...' list -
Code:
A-B-C-D-E
B-F-G-H
G-I
F-J
With that in a text file it is fairly easy for a computer program to analyse what that means -
Code:
Starting points

  A

Terminating points

  E  H  I  J

Track Sections

  A-B
  B-C  B-F
  C-D
  D-E
  F-G  F-J
  G-H  G-I

Points

  B-C/F
  F-G/J
  G-H/I

Routes

  A-E (A-B-C-D-E)
  A-H (A-B-F-G-H)
  A-I (A-B-F-G-I)
  A-J (A-B-F-J)
And also to generate the routing rules which a PICAXE would need -
Code:
Route(A_E) ; A-B-C-D-E
Clear(A_B) ; Track between A and B must be clear
Point(B_C) ; Points at B must point to C
Clear(B_C) ; Track between B and C must be clear
Clear(C_D) ; Track between C and D must be clear
Clear(D_E) ; Track between D and E must be clear

Route(A_H) ; A-B-F-G-H
Clear(A_B) ; Track between A and B must be clear
Point(B_F) ; Points at B must point to F
Clear(B_F) ; Track between B and F must be clear
Point(F_G) ; Points at F must point to G
Clear(F_G) ; Track between F and G must be clear
Point(G_H) ; Points at G must point to H
Clear(G_H) ; Track between G and H must be clear

Route(A_I) ; A-B-F-G-I
Clear(A_B) ; Track between A and B must be clear
Point(B_F) ; Points at B must point to F
Clear(B_F) ; Track between B and F must be clear
Point(F_G) ; Points at F must point to G
Clear(F_G) ; Track between F and G must be clear
Point(G_I) ; Points at G must point to I
Clear(G_I) ; Track between G and I must be clear

Route(A_J) ; A-B-F-J
Clear(A_B) ; Track between A and B must be clear
Point(B_F) ; Points at B must point to F
Clear(B_F) ; Track between B and F must be clear
Point(F_J) ; Points at F must point to J
Clear(F_J) ; Track between F and J must be clear
The above is all actual data generated from the four-line input file.

The best thing is it all does start with a simple text file which can be easily changed, and one only needs to know the 'from-to' sections one has. And that can be adjusted to be whatever form the actual data for the layout has. Everything else can be determined from that, and, being generated by computer, it has to be correct.

And, as noted, that's pretty much a PICAXE program or Eeprom data ready to use, though which inputs represent each track section or points setting would need to be defined.

So I would probably soften my stance from 'don't do anything' to 'get the above done, and worry about the algorithm later'. That better reflects the project as a whole -

Having the track layout data
Having the routing rules
Having a routing algorithm

The first two can be completed without the third, and the third can start by being a 'can we fully complete a route?' affair.
 

hippy

Technical Support
Staff member
Here's an interesting one to ponder, routing a train A-B-C-E-B-C-D ...
Code:
A      B       C
o------o-------o------o D
      /         \
     (           )
      \    E    /
       `---o---'
I'm guessing one would have four routes -

A-D (A-B-C-D)
A-E (A-B-C-E)
E-D (E-B-C-D)
E-E (E-B-C-E)
 

erco

Senior Member
Start small with a portion of your layout. You'll be overwhelmed and paralyzed by such a big project if you try to do it all in one pass. How many simultaneous equations can you solve?
 

hippy

Technical Support
Staff member
You'll be overwhelmed and paralyzed by such a big project if you try to do it all in one pass
The general principle of starting small, then incrementally adding to it, does apply, to this as much as any other project.

The advantage of an auto-generating system is, as long as what is being generated is sound it will all be sound, no matter how small or complex a layout is. And, so long as that complexity can be simply specified, it all becomes quite easy,

For example -
Code:
       A     B    C   D   E    F      G      H
   o---o-----o----o---o---o----o------o------o---o I
        \            /   /      \           /
         \   J    K /   /   L  M \    N  O /
          `--o-----o--------o-----o---o---o------o P
            /         /    /           \
         Q /       R /  S /      T    U \    V
   o------o---------o----o--------o------o---o---o W
                                 /            \
                                 \      X     /
                                  `-----o----'
32 sections, 17 points, 36 potential routes, nearly 600 routing rules, all generated from just a few lines of specification -
Code:
A-B-C-D-E-F-G-H-I
A-J-K-L-M-N-O-P
Q-R-S-T-U-V-W
F-M
K-D, N-U, O-H
Q-J, R-E, S-L
V-X*-T
Adding that V-X-T loop added 10 extra potential routes and 150 route rules. Not all of those will be required by the operator but It would be a long slog to update hand-crafted code, to get it right, by hand.
 
Sorry for the slow reply guys, I've been doing some testing with the various options on a PICAXE, Teensy and an Arduino all at once to try and work out what the best option should be...

But I think you're right Hippy - I'm rushing in a little too quickly without properly defining what needs to be done... I'll get that worked out first over the weekend and take it from there.

In terms of I/O, that is easily going to overwhelm a single PICAXE - the plan is to have a CANbus network around the layout, with individual nodes being Microchip PICs which have native CAN support (with a transceiver). Still undecided as to what the interlocking will actually be - a PICAXE or something else...

For display I've got a large control panel which is an input device and a display for states of all input items (track circuits etc.); for the display portion I've already posted sample code on the "completed projects" section.

I'll update when I have more :)
 

Dartmoor

Member
Hippy has done a great job & is on the right track.
Something which may help here is the definition of a "route"?
It can mean the entire journey for a train but it can also mean "signal to signal" or "movement authority" (with virtual signals).
The signals (real or virtual) should be placed at exit from sidings/platforms and protecting points/level crossings/tunnels/viaducts etc. They should normally be > max train length apart. Plus numerous other rules.
This breaks the network up into small sections but in manageable numbers.
So, if a route/signal section contains the same track circuit/section as another route, then they conflict.

Attached is a spreadsheet (could be a text file) which simply lists each route (prefix 'R') with track circuits 'T' & points 'P' that are within that route.
The points are defined as 'n' & 'r' (normal or reverse) but could equally be left or right.
With the data presented this way, it is possible to auto-generate a list of routes that conflict and must not be set together. Similarly, it is possible to read which track circuits are required to be clear/free after the conflicting route has been used.
Note: in the example, even signal numbers in one direction & odd in the opposite. Route suffix is 'A' to left with 'B' to right (continue with higher letters for further to right) & the (M)/(S) = main or shunt route etc.
Don't know if this helps?
 

Attachments

hippy

Technical Support
Staff member
It is quite a challenging problem. My opinion is still that key to success is having a good virtual model of what the complete layout is, sections, points, direction of travel, signals, and a simple means of describing that virtual layout, which is good enough to derive all information which may be needed from that.

That's all easy enough for point-to-point configurations, but it soon gets complicated for sidings with bi-directional travel, single track running elsewhere, points and sections which join up and down tracks, three-way points, cross-overs, and even, it seems, non-point joining of tracks.

All that has to be handled, all that has to be representable in the description language, and it has to be capable of being parsed with correct meaning to extract whatever information is needed from it.

And on top of that there's the how the replica implements things, whether controlling trains by DCC or similar, or by selectively powering tracks. Whether it's dynamic, responsive to signalling, or more preset-then-go.

I can see why one might choose a simpler inter-lock system and define it all as IFTTT "if this then that" rules, crafted by hand rather than generated from a virtual model.

And it doesn't help if one isn't familiar with the terminology used, the world of railways and replicas of them. I couldn't tell you the difference between points, turnouts, crossings, crossovers and slips.

And this one amused me. Where would one even start ...

https://qph.fs.quoracdn.net/main-qimg-4b7cf36c24fcd4341122152662e27d58-c

I think the bottom line is that "it's harder than it first appears".
 
Top