User:Andrew Nikitin/adjuster

From J Wiki
Jump to navigation Jump to search

Interactive parameter adjustment

Background

I noticed that sometimes while using J I run same sentence many times in a row with small changes in values involved. The goal is to evaluate the result visually, make informal judgement of which of the parameters needs to be changed and by how much, change the parameter and repeat the cycle until the result is “pleasing”. Important subclass is changing parameters so that some display (plot or drawing) looks pleasing to the eye.

The following form provides convenient user interface to this tweak-run-evaluate-repeat scenario. [{{#file: "adjust.ijs"}} Download script: adjust.ijs ]

cocurrent 'jzadjust'
«utilities»
«data»
«form»
«event_handlers»
«locale_cover»

Utilities

[{{#file: "utilities"}} Download script: utilities ]

controlgrid=:(,~"0/&([: +/\ }:@(0&,)) ,"1 ,~"0/)&,
moveby=:+/@,:"1

controlgrid generates matrix of xywh vectors (3d array) for each pair of given lists of heights (x) and widths (y)

moveby simply adds offset to x and y portion of xywh vector [{{#file: "utilities"}} Download script: utilities ]

require 'regex'
template=:(4 : 0)"1
  m=. '%(\d+)%?' rxmatches x
  i=.(#y) <. (1&{"2 m) ".@>@rxfrom x
  <(i{(":each y),<,'?') (0&{"2 m) rxmerge x
)

Substitutes %0% ... %n% in x with string representation of elements of y, which is boxed list. [{{#file: "utilities"}} Download script: utilities ]

once=: [: ".^:((('=:'&-:)@>@{: *. (0>4!:0)@{.)@(2 {. ;:)) ;._2 ,&LF

once performs the assignment only if the name is not yet defined.

Generate wd from

[{{#file: "form"}} Download script: form ]

adjust=:3 : 0
«parse_input»
wd 'pc adjust closeok;pn "Adjuster (',(>COCREATOR),')";'
«generated_grid»
«quiet_checkbox»
«exec_textbox»
wd 'pas 5 5;pshow;'
«init_formcontrols»
:
  exec=:x
  adjust y
)

We generate appropriate number of buttons and edit controls and quiet checkbox and exec textbox as descrbed below.

Optional left argument specifies value for exec string. [{{#file: "parse_input"}} Download script: parse_input ]

  if. -.''-:y do.
    NAMES=:2 {.!.(<0 1)"1 ,. ;:^:(0=L.)y
    STEPS=:>{:"1 NAMES
    NAMES=:{."1 NAMES
  end.
  assert. 0=4!:0 <'NAMES'
  assert. 2=#$STEPS
  assert. STEPS=&# NAMES
  assert. 2={:$STEPS

The list of names to ajust can be specified as a boxed list or ;:-string. Appended to a list of names can be a boxed 2-list of increment type and increment value (both can be changed during runtime) -- in this case y is n × 2 boxed array.

Names must be present and be numeric scalars in caller locale.

If argument is empty, the form tries to reuse values from previous run (if any).

Increment types and values are stored in STEPS array. Types are indices into OP global: [{{#file: "data"}} Download script: data ]

OP=:2 3$[`+`-`[`*`%

We will have 2 flavors of increase/decrease -- additive and multiplicative. The verbs that perform various increases/decreases are collected in OP gerund. One row per flavor. 0-th column = verbs for 'no change', 1-st = verbs for increase (x=value, y=increment),' 2-nd (_1-st) -- for decrease (x=value, y=decrement)

Initially, all steps are additive and equal to 1. That is, decrease button decreases paramter by one and increase increases it by one. To change step, in the editbox type, for example, +5 and hit Enter. This will change a step value to 5, and step flavor to additive and increase previous value of parameter by 5. -5 will set same step, but will decrease parameter by 5. Here + and - indicate that entered value is not a new value for a parameter itself, but a new step. To enter negative value of a parameter, use _ for negative sign.

*1.1 will set a multiplicative step of 1.1 -- increase/decrease commands will multiply/divide parameter by this value.

+10% is another way to enter *1.1 and -10% is equivalent to /1.1, which is not what it usually means, but close enough. [{{#file: "parse_input"}} Download script: parse_input ]

  once 'COCREATOR=:<''base'''

Normally COCREATOR is initialized by _z_ cover (see below) or conew command. In case this did not happened by now, we initialize it with some sensible value.

Generate grid

The grid consists of several almost identical rows, one for each name. Each row of controls provides functionality to update one name. It is referred further as aggregate control. [{{#file: "data"}} Download script: data ]

ET=:0 : 0
xywh %4;cc ccsatic static;cn "%0";
xywh %5;cc %1 edit;
xywh %6;cc %2 button;cn "%8<<";
xywh %7;cc %3 button;cn "%9>>";
)

This is a template for our aggregate control. Aggregate control consists of several standard windows controls that work together to provide required functionality. This specific control consists of:

  • label (parameter 0)
  • edit box
  • button to increase value
  • button to decrease value

To distinguish events from edit box and buttons they will need to have unique id assigned (parameters 1-3). These elements are located in a row (defined by rectangles in parameters 4-7). In addition, increase and decrease buttons need unique hotkeys, AKA accelerators (parameters 8-9). [{{#file: "data"}} Download script: data ]

HOTKEYS=:'12qwaszx34erdfcv56tyghbn78uijkm,90opl;./-=[]'

List of hotkeys to assign to increase/decrease buttons. [{{#file: "generated_grid"}} Download script: generated_grid ]

  w=.30 40 20 20
  h=.15
  r=.(h#~#NAMES) controlgrid w
  v=.0 0,~(10++/w),-PARPERCOL*h
  coloffs=.v*/~(#NAMES){.PARPERCOL # i.1>.>.PARPERCOL%~#NAMES
  r=. <"1 ]5 5 0 _3 moveby coloffs moveby r

w is list of widths of label, editbox, decrease button, increase button respectively.

h is a height of a single row of controls. [{{#file: "data"}} Download script: data ]

PARPERCOL=:10

PARPERCOL is maximum number of controls that can be placed in the column (form height direction). If there are more parameters than this value, new column will be added. This way form has limited height, but unlimited width. Unlimited width is easier to handle because in Windows top form bar spans across its width and is always available to grab it with mouse and move form left and right. When the form is too tall, lower portions of it cannot be accessed by just dragging the form by its top bar. [{{#file: "generated_grid"}} Download script: generated_grid ]

  CONTROLS=:('ac' , ":)&.> i.3,~#NAMES
  n=.(, '=:'"_)&.>NAMES
  hk=.(#NAMES) {. _2 <@('&' , ] , ': '"_)"0\ HOTKEYS
  wd ; ET template n,.CONTROLS,.r,.hk

Form control names (generated in form ac%d) are stored in CONTROLS array for event processing. [{{#file: "init_formcontrols"}} Download script: init_formcontrols ]

  wd&> 'set %0 *%1' template (0{"1 CONTROLS) ,. {.@(do__COCREATOR :: 0:) &.> NAMES

Values for textboxes come from the values of the appropriate nouns in caller locale.

Place the rest of form controls

[{{#file: "quiet_checkbox"}} Download script: quiet_checkbox ]

  ltrb=.0 1 _2 _1{(<./ , >./) ,/ (2&{. , [: +/ _2 ]\ ])"1 >r
  qr=. (0 5+0 _1 { ltrb),(-/2 0{ltrb),11
  wd 'xywh ',(":qr),';'
  wd 'cc quiet checkbox; cn "Quiet"'

Checkbox: whether to show the expression and its result every time we change the parameter. It is located under the autogenerated form controls. [{{#file: "init_formcontrols"}} Download script: init_formcontrols ]

  once 'quiet=:''0'''
  wd 'set quiet ',quiet

Checkbox is clear by default. [{{#file: "exec_textbox"}} Download script: exec_textbox ]

  er=.11 (_1)} 0 12 moveby qr
  wd 'xywh ',(":er),';'
  wd 'cc exec edit es_autohscroll;'

exec textbox is placed underneath quiet checkbox [{{#file: "init_formcontrols"}} Download script: init_formcontrols ]

  once 'exec=:'''''
  if. ''-:exec do. exec=.;}.,(<','),.NAMES end.
  wd 'set exec *',exec

exec string is either reused from previous run, or initialized with the expression "list of all parameters". Unless, of course, it is specified explicitly as left argument to adjust.

Event hadlers

[{{#file: "event_handlers"}} Download script: event_handlers ]

adjust_exec_button=:3 : 0
hwnd=.wd 'qhwndp'
if. -.0".quiet do.
  smoutput '   ',exec
  smoutput do__COCREATOR exec
else.
  do__COCREATOR exec
end.
wd 'psel ',hwnd,';pactive;'
)

This is event handler to process ENTER pressed on the exec editbox. Also called when any of the parameters changes.

If exec sentence opens a window (like plot), the focus will be trasfered to it and shortcut keys will no longer work. Hence the last line here -- to activate adjust form so that it can continue to receive hotkeys. [{{#file: "event_handlers"}} Download script: event_handlers ]

adjust_default=:3 : 0
  if. -.'button'-:'systype' wdget wdq do. return. end.
  c=.CONTROLS=<'syschild' wdget wdq
  if. 0=+./,c do. return. end.
  row=.{.I.+./"1 c
  col=.{.I.+./   c
  v=.".>0{row{CONTROLS
  i=.'+-*/' i. {.v
  st=.i{0 0 1 1,0{row{STEPS
  vn=.('_' , (>COCREATOR),'_'),~ >row{ NAMES
  if. i<3 do.
    v=.1}.v
    p=.'%'={:v
    st=.st+.p
    sv=.(1&".)`(1.1&".)`(1 + 100%~10&".)@.(st+p) }:^:p v
      NB.                ^^ not quite; y-10% is not y/1.1
    v=.".vn
  else.
    'st sv'=.row{STEPS
    v=.{.0".v
  end.
  STEPS=:(st,sv) row} STEPS
  v=.v (st{OP)@.(i=.i{1 _1 1 _1, col { 0 _1 1) sv
  (vn)=:v
  wd 'set ',(>0{row{CONTROLS),' *',":v
  wd 'setfocus ',>(row{CONTROLS){~i{0 2 1
  adjust_exec_button ''
)

Generic handler that processes events from generated controls, performs assignments and executes the verb.

Locale cover

Similar to plot and grid. [{{#file: "locale_cover"}} Download script: locale_cover ]

adjust_z_=:3 : 0
coinsert_jwadjust_ 'jzadjust'
COCREATOR_jwadjust_=:coname ''
adjust_jwadjust_ y
:
coinsert_jwadjust_ 'jzadjust'
COCREATOR_jwadjust_=:coname ''
x adjust_jwadjust_ y
)

There is a working adjustment form jwadjust. It is reused every time user calls adjust verb. Several independent forms may be created in numbered locale with ADJ=:conew 'jzadjust' and subsequent call to adjust__ADJ.

Example

First initialize parameters.

require 'plot'
INPUT=:50
LEFT=:20
RIGHT=:40

Create adjustment form with

adjust 'INPUT';'LEFT';'RIGHT'

Then enter the following in the exec box:

('xrange ',":LEFT,RIGHT)plot *:i.INPUT

and start changing parameters.

To save the current value of the parameters in compact form use:

   save=:(('(''' , ;:inv , ''')=:'"_),[: ": ".&>)
   save NAMES_jwadjust_


   A1=:conew 'jzadjust'
   A2=:conew 'jzadjust'
   adjust__A1 'INPUT';'LEFT'
   adjust__A2 'INPUT';'RIGHT'

See also: