Archive for May, 2019

Potentials – Accurate Registration

Friday, May 24th, 2019

Building Great Code

For the last several days, I have been noodling on a problem with Potentials that hasn't been the top thing to do - until today. And I wanted to figure this out once and for all, and get things really lined up properly. The problem starts with the simulation grid. It's an nxm grid, where the solution will be calculated on each of the grid points - exactly. It's then up to me to visually represent that as best I can.

The initial code took each of these points and created a rectangle for each on the viewport of the NSView, and then selected the best color, and filled in that rectangle. So far, so good.

But then the contour lines required that we take groups of 4 points and (possibly) draw a line in the box they defined. Now we have a problem. As you look at the box for a contour line, it will be "colored" by the node value in the lower-left corner. Not ideal. This means that there would be a row, and a column, of rectangles that represent the edge nodes of the simulation - but no way to put contour lines in those boxes.

The solution was to treat the rectangle fill the same way as the contour lines - to take all four points in each square and compute a single value for the painting that rectangle. For a first cut, it seemed reasonable to have a simple MAX() function of all four values - yes, this will inflate the effect of an isolated node, but that's OK... it's the maximum value - in that area. I can live with that.

The results are really nice. The contour lines and the items under simulation and colors line up very nicely. This is a blown up section around the point conductor in the example:

Potentials Registration

At the same time, the contour lines now go to the edge of the display, as they should, and if I need to change the algorithm for the colorization of the rectangles - based on the four point values - that's easy enough to change.

Looking better and better. 🙂

Potentials – Respect the Ratio

Wednesday, May 22nd, 2019

Building Great Code

Today I noticed that the display code for Potentials was filling the display viewport - and distorting the shapes in the simulation. For example, a "square" could be made to look like a long rectangle, simply by resizing the window. This wasn't right. So today I spent a little time to fix this.

The code wasn't that hard, and to save a little time, I computed the drawing "origin" as well as the scale factor for the drawing to the NSView so that each part of the drawing didn't have to recompute these. Nothing fancy - just scale to the one dimension that will fit in the viewport, and then fix that, adjust everything else.

Potentials where the Ratio is Right

At this point, you can see the added white "letter boxing" on the window as we have respected the ratio of the workspace, and the drawing is being constrained in the vertical dimension. Simple, but very nice.

Little by little, it's getting better. 🙂

Sometimes… Performance Tuning is Just Exercise

Wednesday, May 22nd, 2019

Speed

This morning, I was taking another crack at increasing the performance of the graphing in Potentials, as my initial tests of just draw red seemed to show that I could cut a significant amount of time off the drawing, if I didn't have to allocate all those NSColor objects. Seemed like a reasonable thing to try, and after all... performance tuning can be fun.

So it's not like the CGColor or NSColor would allow me to directly access the values of red, green, and blue - so I had to move them from the NSColor instance into a simple array of CGFloat values. No big deal - now I'm passing around a simple, fixed-size array that is easily indexed, and because I can make them on the stack, there's no way these are leaking.

Then I needed to peel back the existing methods on ResultsView and make it so that they allowed for passing in, and returning, arrays of CGFloat values. Again, this is a simple refactoring, as I could then simply use these new methods within the existing methods, and I didn't loose any capabilities of using NSColor arguments. Still good.

Finally, I was then able to update the drawing method and have it convert the input colors to linear space, and arrays of CGFloat values, and then run the same processing on these arrays of CGFloats, and we had what we'd hoped for.

Almost.

What I saw was that there was 2 msec difference - out of 17+ msec - that this changed. Nothing. So I backed out the changes, and realized that I had a new respect for the macOS developers - they have really tuned this machine to make the AppKit/Foundation way of doing things about as fast as humanly possible - and the difference with just plotting red was that of a single color versus a range. This was the case all along, and I was mistakenly thinking that I was seeing some effect that wasn't really there.

So it's not faster... but I am wiser about the inner workings of macOS and drawing, and I'll be sure to remember this in the future. 🙂

Potentials – Smoother Contour Lines

Monday, 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

Wednesday, 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

Thursday, 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

Tuesday, 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

Monday, 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

Sunday, 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

Saturday, 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.