carpalx - keyboard layout optimizer - save your carpals
Carpalx optimizes keyboard layouts to create ones that require less effort and significantly reduced carpal strain!

Have ideas? Tell me.

the best layout

Partially optimized QWKRFY and fully optimized QGMLWY layouts are the last word in easier typing.

the worst layout

A fully anti-optimized TNWMLC layout is a joke and a nightmare. It's also the only keyboard layout that has its own fashion line.

download and explore

Download keyboard layouts, or run the code yourself to explore new layouts. Carpalx is licensed under CC BY-NC-SA 4.0.

layouts

Download and install the layouts.

Basic Layout Optimization

configuration file : etc/tutorial-03.conf
output : out/tutorials/03

Configuration

Here's what you're here for - keyboard layout optimization. For this tutorial, we'll do a quick-and-dirty optimization. We'll start with the QWERTY layout and a very short English corpus. To speed things along, I've set the triads_min_freq to 10 - any triads that appear in the corpus fewer than 10 times will not be used.

...
action = loadkeyboard,loadtriads,optimize,quit
...
corpus  = ../corpus/books.veryshort.txt
mode    = english
triads_overlap  = yes
triads_min_freq = 10
...
keyboard_input  = keyboards/qwerty.conf
...

You can replace the corpus with books.short.txt, or if you would like an even larger corpus, books.txt.

The short corpus (corpus/books.short.txt) contains the first 50,000 lines (566,916 words) of the full corpus (corpus/books.txt). The very short corpus (corpus/books.veryshort.txt) contains the first 5,000 lines (50,308 words) of the full corpus. You can further limit the number of triads that are parsed by using triads_max_num (this limits the number of total, not unique, triads parsed). The time required for each iteration of optimization will be proportional to the number of unique triads that you parsed in; thus, limiting the corpus size may not necessarily result in a commensurately shorter execution time.

The annealing parameters are defined in the <annealing> block.

<annealing>
action     = minimize
iterations = 1000
t0         = 10
p0         = 1       
k          = 10
minswaps   = 1
maxswaps   = 3
onestep    = no      
</annealing>

The t0 and k variables control the annealing cooling schedule. minswaps and maxswaps control how many random character swaps are performed to generate a new state.

The output keyboard layout, optimization parameters and current annealing status are reported in the keyboard_output file. The keyboard file contains a random 6 character string to prevent concurrent instances of carpalx from clobbering report files.

keyboard_output = /home/martink/work/carpalx/dev/out/tutorials/03/tmp-__$CONF{runid}__.conf
...
runid = __join("", map { chr(97+rand(26)) } (0..5))__

run-time reporting

> bin/carpalx -conf etc/tutorial-03.conf > out/tutorials/03/out.txt

At the requested interval (defined by report_period and report_filter), the current keyboard state and annealing parameters will be written out the keyboard_output file. For each iteration, the current keyboard state and parameters will be reported to STDOUT. The STDOUT report looks like this:

iter      7 effort 2.641495 -> 2.613979 d -0.02751655 p 0.99705318 t 9.32393820 better/accept/repor
t cpu 0.097902
------------------------------------------------------------
` 1 2 3 4 5 6 7 8 9 0 - =     ~ ! @ # $ % ^ & * ( ) _ +
q g k d o a v x b z [ ] \     Q G K D O A V X B Z { } |
s n f t y h e i m ; '         S N F T Y H E I M : "
u r c p j w l , . /           U R C P J W L < > ?
------------------------------------------------------------
...
iter    239 effort 2.995385 -> 2.511309 d -0.48407581 p 0.58960890 t 0.91629684 better/accept/repor
t cpu 0.097838
------------------------------------------------------------
` 1 2 3 4 5 6 7 8 9 0 - =     ~ ! @ # $ % ^ & * ( ) _ +
q g k d o a v x b z [ ] \     Q G K D O A V X B Z { } |
s n m h y t e i l ; '         S N M H Y T E I L : "
u r c p j w f , . /           U R C P J W F < > ?
------------------------------------------------------------
...
iter    862 effort 1.802331 -> 1.799707 d -0.00262427 p 0.23358457 t 0.00180460 better/accept/repor
t cpu 0.097255
------------------------------------------------------------
` 1 2 3 4 5 6 7 8 9 0 - =     ~ ! @ # $ % ^ & * ( ) _ +
b c p w y j m u l x [ ] \     B C P W Y J M U L X { } |
n d t a s i o e h ; '         N D T A S I O E H : "
q k r f z v g , . /           Q K R F Z V G < > ?
------------------------------------------------------------
...
iter    956 effort 1.786068 -> 1.778577 d -0.00749106 p 0.00002426 t 0.00070493 better/accept/repor
t cpu 0.098536
------------------------------------------------------------
` 1 2 3 4 5 6 7 8 9 0 - =     ~ ! @ # $ % ^ & * ( ) _ +
w m p l c q y u b x [ ] \     W M P L C Q Y U B X { } |
d n t a s i o e h ; '         D N T A S I O E H : "
j k r f z v g , . /           J K R F Z V G < > ?
------------------------------------------------------------
Total time spent optimizing: 98.45551 s

For each iteration, the candidate keyboard layout is shown (lower case on the left and upper case on the right). The effort transition from previous to current state is shown, along with the effort difference (d), the probability of transition to the new state (p), and the cooling parameter (t) for the current iteration. Each iteration is also annotated with FLAG1/FLAG2 where FLAG1=better|worse and FLAG2=accept|reject. FLAG1 indicates whether the new state is more desirable (lower effort) and FLAG2 indicates whether the new state is accepted. Lower effort states are always accepted and higher effort states are accepted based on the probability p. The time to perform the iteration is also shown.

Concurrently, the keyboard_output file is updated each time the energy for a new state is lower than previously seen (note the difference between report_filter=lower and report_filter=lower_monotonic). For this specific run, the file is tmp-rhdbnd.conf. The format of this file is the same as the keyboard_input file. Therefore, you can resume optimization at a later time or generate reports for this keyboard layout.


<current_parameters>
iter               = 956
effort             = 1.77857723249378466
deffort            = -0.00749105590454603686
update_count       = 465
t                  = 0.00070492798662117875
</current_parameters>

<annealing_parameters>
t0                 = 10
k                  = 10
maxswaps           = 3
p0                 = 1
action             = minimize
iterations         = 1000
onestep            = 0
minswaps           = 1
</annealing_parameters>

<keyboard>
<row 1>
keys = `~ 1! 2@ 3\# 4$ 5% 6^ 7& 8* 9( 0) -_ =+
fingers  = 0 1 1 2 3 3 3 6 7 7 8 9 9
</row>
<row 2>
keys = w m p l c q y u b x [{ ]} \|
fingers  = 0 1 2 3 3 6 6 7 8 9 9 9 9
</row>
<row 3>
keys = d n t a s i o e h ;: '"
fingers  = 0 1 2 3 3 6 6 7 8 9 9
</row>
<row 4>
keys = j k r f z v g ,< .> /?
fingers  = 0 1 2 3 3 6 6 7 8 9
</row>
</keyboard>

After this run, we've lowered the typing effort of the corpus from 3 to 1.8. The <current_parameters> block indicates that effort was lowered in 465 distinct steps and the lowest energy was seen at iteration 956.

Simulated annealing is stochastic optimization method. I suggest you run carpalx multiple times with the same parameters and choose the best result. Performing multiple optimizations will also give you an indication of how many general families of layouts you are converging to. For example, running the process again (tmp-nfduur.conf), yields a layout with an effort of 4.8.

# first run (tmp-yryhmp.conf) (effort = 1.779)
w m p l c q y u b x [{ ]} \|
 d n t a s i o e h ;: '"
  j k r f z v g ,< .> /?

# second run (tmp-ippjnl.conf) (effort = 1.758)
x y d l g b f s w v [{ ]} \|
 u h e i r t a o n ;: '"
  z j k c q m p ,< .> /?

In this case, the layouts are quite different. Although you would need to perform more optimizations to determine whether the algorithm converges, I would do so with more iterations (e.g. 10,000 instead of 1,000). The advanced optimization tutorial discusses the effect of adjusting the annealing parameters, such as the cooling schedule.