Potentials – Smoother Contour Lines

May 20th, 2019

Building Great Code

Today at lunch, I was scanning the class notes on the Contour Lines I'd used in Potentials, and I was thinking that their solution of sub-dividing the grid ever smaller - until you get to the resolution of the display was nice, but that there was a better way - analog. Meaning, to ratio the value of the contour, and the relative values of the grid points. My notes were pretty simple:

Contour Lines Notes

And when we look at the previous work, it's clear where the nodes are, as it's crude, and not very smooth at all. But when we take the values from the data and then test them:

  // grab the four corner values from the data
  vul = _values[r+1][c];
  vur = _values[r+1][c+1];
  vll = _values[r][c];
  vlr = _values[r][c+1];
  // now compute the in/out state of the four corners
  ul = (vul < value);
  ur = (vur < value);
  ll = (vll < value);
  lr = (vlr < value);

then the code for the individual line segments of the contour are simply:

  // line from left to top
  sy = (value - vll)/(vul - vll);
  pbeg = NSMakePoint(x, y+dy*sy);
  sx = (value - vul)/(vur - vul);
  pend = NSMakePoint(x+dx*sx, y+dy);

where the effect of sx and sy are to modify the value from 0.5 in the previous version, which is half-way between nodes, to that percentage represented by the values of the nodes, and the contour. This makes a huge difference:

Potentials with Smooth Contours

And it's just amazing how much nicer it looks. The code isn't doing anything that complex - it's just doing the work a little smarter... and that is just elegant.

Potentials – Added Contour Lines

May 15th, 2019

Building Great Code

Today I spent some time refactoring the [ResultsView -drawRect:] method in Potentials as it was a monster method with all kinds of access to ivars, and lots of code - and it really needed to have setters and getters, and the code needed to be broken up so that it was a lot easier to manipulate and compose. That wasn't too bad. Then it was time to look at the Contour Lines.

I ran across this slide deck for a class from the University of Edinburgh on this exact subject. Turns out it was a lot simpler than I had thought. By simply using the gridding I'd used for the solution, it was possible to think of the contours as separators and not just lines of "constant value" - because then it comes down to 16 different patterns where the state of each node value with respect to a threshold is concerned.

Then it was a simple task of drawing the lines, and boom! Done!

Potentials with Contour lines

Sure... there's a lot I can do to make this nicer - reduce the gird size... go non-uniform mesh and refine the mesh based on the magnitude of the electric field, and at less than 10 msec per solution set, I could easily refine this 5 or 6 times and come up with something really pretty nice.

But Wow... this has been something I've wanted for years... and now it's checked-in, and it runs faster than I ever would have imagined. Life is good. 🙂

Potentials – Adding the Inventory

May 9th, 2019

Building Great Code

The latest addition to Potentials has been that of the outlines of the elements in the simulation - lines, points, rectangles, etc. These can be charge sheets, conductors, dielectrics, etc. - but until now, only their effects have been seen. The point is to make these items visible on their own, so that their boundaries can be seen, as well as their effects.

Since all these had to be in-place in the context of the -drawRect: method on the NSView subclass, it meant that we had to be passed in with the plotting data that was the results of the simulation. In order to make this scale well, with redraws of the NSView, we needed to pass this in with [0..1] on the linear dimensions of the drawing surface.

What we settled on was to add to the BaseSimObj a method:

/*!
 This method returns an NSDictionary with the Quartz 2D drawing data
 and keys to indicate *how* to draw that object. The axis measurements
 are normalized to [0..1] so that scaling this is very easy, and it's
 placed in the workspace so that as that region is drawn, this object
 is in the correct location. This is essential so that this guy can
 be drawn on the simulation results.
 */
- (NSDictionary*) drawingInfo:(SimWorkspace*)ws;

so that each type of object could place itself in the drawing space on the unit axes. The data is returned as an NSDictionary because it's simply a few pieces - the type of drawing needed to be done, and the data for that drawing. Here is the line's implementation:

- (NSDictionary*) drawingInfo:(SimWorkspace*)ws
{
  if (ws != nil) {
    NSRect        wsr = [ws getWorkspaceRect];
    NSPoint       beg = [self getStartPoint];
    NSPoint       end = [self getEndPoint];
    // map the point to [0..1] on each axis for plotting
    beg.x = (beg.x - wsr.origin.x)/wsr.size.width;
    beg.y = (beg.y - wsr.origin.y)/wsr.size.height;
    end.x = (end.x - wsr.origin.x)/wsr.size.width;
    end.y = (end.y - wsr.origin.y)/wsr.size.height;
    return @{@"draw" : @"line",
             @"from" : [NSValue valueWithPoint:beg],
             @"to" : [NSValue valueWithPoint:end]};
  }
  // there's nothing we can possibly do without a workspace
  return nil;
}

where the required key is draw, and that is a simple NSString of the type of drawing to do, which will then be interpreted in the -drawRect: method. Simple.

Another is the rectangle:

- (NSDictionary*) drawingInfo:(SimWorkspace*)ws
{
  if (ws != nil) {
    NSRect        wsr = [ws getWorkspaceRect];
    NSRect        rect;
    rect.size = [self getSize];
    // move the center of the rect to the origin for the NSRect
    rect.origin = [self getLocation];
    rect.origin.x -= rect.size.width/2.0;
    rect.origin.y -= rect.size.height/2.0;
    // map the point to [0..1] on each axis for plotting
    rect.origin.x = (rect.origin.x - wsr.origin.x)/wsr.size.width;
    rect.origin.y = (rect.origin.y - wsr.origin.y)/wsr.size.height;
    rect.size.width /= wsr.size.width;
    rect.size.height /= wsr.size.height;
    return @{@"draw" : @"rect",
             @"data" : [NSValue valueWithRect:rect]};
  }
  // there's nothing we can possibly do without a workspace
  return nil;
}

and as long as the drawing data can be put into an NSDictionary, we're good to go.

Then we needed to add a section to the -drawRect: method that would look at the list of all the items in the inventory, and for each, look at their drawing directions, and draw them in the context. This is the code for the first one - a line:

  for (NSDictionary* info in [self getInventory]) {
    // everything draws to the view, scaled on the
    NSString* draw = info[@"draw"];
    if ([draw isEqualToString:@"line"]) {
      // get the specifics for the line from the data
      NSPoint     beg = [info[@"from"] pointValue];
      NSPoint     end = [info[@"to"] pointValue];
      // map it to the viewport for drawing
      beg.x *= sx;
      beg.y *= sy;
      end.x *= sx;
      end.y *= sy;
      CGPoint     line[] = {beg, end};
      NSLog(@"[ResultsView -drawRect:] - drawing line inventory: (%.2f, %.2f) ->
              (%.2f, %.2f)", beg.x, beg.y, end.x, end.y);
      // draw the line in the viewport
      CGContextBeginPath(myContext);
      CGContextSetLineWidth(myContext, 1.0);
      CGContextSetRGBStrokeColor(myContext, 0.0, 0.0, 0.0, 1.0);
      CGContextAddLines(myContext, line, 2);
      CGContextStrokePath(myContext);

The interesting addition here is that when drawing a path, you really have to begin the path, add the path elements, and then stroke the path. Not bad, but it took a little bit to find that in an example to make the elements visible.

In the end, we now have the inventory:

Potentials with Inventory

And the speed is impressive. The simulations are being calculated in under 5 msec, and the drawing is being done in under 14 msec. This means that it's going to be possible to make a graphical builder - eventually, and have the results appear quite nicely. 🙂

Potentials – Added Plot Switching

May 7th, 2019

Building Great Code

This afternoon I found an article about creating contours on data, and while it looks very reasonable, it was more than I could dig into and really get done in a few minutes. So, I looked to be able to flip between plots on the app - either V(x,y) or E(x,y), and to make it simple - I decided to just use the menu items and that would then make keyboard shortcuts easy, and the state of the plot would be reflected in the menu.

Potentials Plot Switching

With the menu items added, I simply had to make the IBOutlets for the the NSMenuItems, and then make IBActions for the menu item selections, and the code was all pretty simple:

- (IBAction) plotVxy:(id)sender
{
  BOOL                 error = NO;
 
  SimWorkspace*        ws = nil;
  // first, make sure we have a workspace to use
  if (!error) {
    ws = [self getWorkspace];
    if (ws == nil) {
      error = YES;
      NSLog(@"[MrBig -plotVxy:] - there is no defined workspace with which to run
              this simulation. Please make sure there is before calling this method.");
    }
  }
 
  // next, see if we need to switch views
  if (!error) {
    if ([[self getPlotExy] state] == 1) {
      [[self getPlotVxy] setState:1];
      [[self getPlotExy] setState:0];
      // plot the Voltage on the workspace
      if ([ws getResultantVoltage] != nil) {
        [[self getResultsView] plotVoltage:ws];
      }
    }
  }
}

And we automatically implement the mutual exclusive nature of the toggle, and if we have no data to plot, we don't waste time, but if we do, we switch the view. Simple.

These little updates are a lot of fun, and it's certainly an aspect of macOS coding that I haven't done a lot of - Graphics.

Potentials – Adding the Electric Field to the Plotting

May 6th, 2019

Building Great Code

In the original code, we did a point-by-point calculation of the magnitude and direction of the electric field based on the results of the solution of the potential on the workspace. This was fine when it was just writing it out to the file, but when we want to do the plotting of the data, it makes sense to compute the magnitude and direction of the field for all points, and save them. So that's what we've added in this most recent update.

Potentials adds Electric Field

We added the ivars, setters and getters to the SimWorkspace class, and then simplified the getters to use the ivars and now the computed values. Finally, we added in the calculation of the field to the end of the -runSim: method, and we got what we needed. At this point, we need to be able to switch between the voltage and electric field, and then add isobars, and finally, the directions on the electric field.

There is plenty yet to do, but it's really starting to take shape, and as we keep making progress, the app is really starting to come together. 🙂

Potentials – Adding Multiple Colors to Graphing

May 5th, 2019

Building Great Code

The graphs in Potentials are OK... but they are only interpolating between two colors. It's OK... but it's not as nice as I'd like it. What I wanted was to be able to have several colors in the mix - so I could come up with colors like Black Body Radiation. Then it came to me, that I could bracket the values with an array of colors, like:

  NSColor*    spectrum[] = {[NSColor redColor],
                            [NSColor orangeColor],
                            [NSColor yellowColor],
                            [NSColor cyanColor]};
  int         stages = 4;
  double      dc = 1.0/(stages - 1);

And then, for each point to plot:

  double      x = 0.0;
  int         ilow = 0;
  for (int r = 0; r < _rowCnt; r++) {
    for (int c = 0; c < _colCnt; c++) {
      x = _values[r][c];
      ilow = MIN((int)(x/dc), (stages-2));
      x -= ilow * dc;
      gc = [ResultsView interpolate:(x/dc)
                        withColorsBetween:spectrum[ilow]
                        and:spectrum[ilow+1]];

Where we're picking the value up, then finding the two bordering colors on that value, and then moving the value to the [0..1] range and then interpolating between those two colors.

In the end, it looks great:

Potentials Multi Color Graphing

It's a really nice improvement, and next we have to work on the Electric field.

Potentials – Fixed up the Simulation Results

May 4th, 2019

Building Great Code

This afternoon I was playing with the simulation data for Potentials and started to log out all the values going in, and coming out, of the dgbsv_ call, and I started to wonder if the memory was being cleared as it was returned from malloc. Given that I had seen the vDSP functions, it made sense to use those - as opposed to just calling memset, as they handle doubles and integers, and I didn't want to mess up any framing on the doubles.

When I put in that code, which was really as simple as:

  // clear out the vector with zeros
  vDSP_vclrD(b, 1, ldab*nrhs);

and similar function calls for the other matrix allocations, the code just worked.

Potentials Simulation Fixed

And not just that... you can now edit the input in the editor window, and re-run it. Perfectly. Also, it's easy to reload different simulation sets, and re-run those, as well. This is exactly what I was hoping to see as the next big step. Now I need to be able to plot the electric field as well - magnitude and direction.

Potentials – Adding Plotting of Output Data

May 3rd, 2019

Building Great Code

This morning, I was able to finish up the plotting of the output data in Potentials. It's not generating the right numbers, but the NSView Quartz 2D drawing is working, and the linear color interpolation is doing its job as well. What seems to be the problem is the cLAPACK call to solve the system.

Potentials can Draw

I know this was all working years ago, so it has to be in the cLAPACK code in the Accelerate Framework in macOS - and there, anything is possible. Could be the format of the banded matrix storage changed, or the format of the numbers or structure of the A or b in the equation... but it's something... because I looked at the output, and the numbers just are not right.

The blank area at the bottom is where the data was either NaN or -Inf, and I know that's not right. And then the potential field distribution is all wrong. No way this is right. So I'll come up with a simpler test case, two line conductors, and a smaller grid, and see what's happening in more detail. It shouldn't be too hard to see what's up.

Then, it's just a matter of seeing what's next. Things are taking shape! 🙂

Potentials – Adding Initial Drawing to the App

May 1st, 2019

Building Great Code

Today I got back to Potentials and wanted to add in the initial drawing context to the app - by splitting the UI from being just an editor to an editor at the bottom of the view, and a NSView subclass on the top. The bottom editor will be the complete width of the window, and the height will be fixed at a handful of lines. Then the drawing area is the stretchable area above that.

I had to re-learn a few things - like where the outlets and actions are held, and it turned out that it really was all about the nib file format, and once it was converted to an xib, I was able to find everything where I expected to see it.

Potentials Starts Drawing

There were a few AutoLayout issues that were easy enough to correct with the xib file, and then it was a few simple drawing functions, and it's working just great. I have a lot more to do, but I think the Voltage plots are going to be pretty simple... just a color range on the values, and away we go.

It's nice to be back in ObjC and macOS... such a fun platform. 🙂

Upgrading Old Interface Builder Files

May 1st, 2019

xcode.jpg

This morning I was working on my Potentials app for macOS, and I realized that the Auto Layout constraints were not being set properly, and the Xcode 10.2.1 UI wasn't showing the existing constraints, either. I noticed this was still a nib file, and I know Xcode has moved to xib files many years ago, so I looked up how to upgrade the file's contents.

The solution was pretty easy:

  $ cd Base.lproj/
  $ ibtool MainMenu.nib --upgrade --write MainMenu.xib

and then I can add the MainMenu.xib to the project, and delete the MainMenu.nib from the project. Check it all in, and we're done.

My concern is that Xcode should have said something. Like "Upgrade this file", or "Let me upgrade this file"... but leaving it alone and not really working right - that's not really a good thing to do. Still... I've been using these tools long enough, I had a feeling it was something like this.