[Tutorial] Non-unimodal ternary search

Правка en1, от polosatic, 2024-11-04 12:09:48

Hello everyone. Today I would like to introduce you to a new technique — ternary search on non-unimodal functions. I came up with it during ROI 2024 and it helped me to become a prize winner.

The motivation

There are many examples in CP where we need to maximise or minimise the value of some function $$$f$$$. If the function is unimodal, there is no problem using ternary search. But if it's not, there's nothing to do. Of course, if $$$f$$$ is random, we should check all its values. Fortunately, intuition tells us that if the function is often close to unimodal, we can also use ternary search on a large part of its area of definition.

The intuition

Consider the unimodal function $$$0.03x^2$$$ after adding some noise ($$$sin (x)$$$) to it:

Graph

Intuition suggests that if the noise is not strong, we can still use a ternary search away from the global minimum.

In this example, we can put it more formally — the derivative of the function is $$$0.06x + cos(x)$$$, and when $$$|x| > \frac{1}{0.06} \approx 16.67$$$, adding cosine does not change the sign of the derivative.

The first optimisation

So, the first idea is to run the ternary search using not while (r - l > eps) but while (r - l > C) and then bruteforce all values between $$$l$$$ and $$$r$$$ with some precision. In many cases when $$$f$$$ takes an integer argument there will be no precision issue at all.

The second optimisation

I should mention this blog. It tells us a similar idea of splitting all argument values into blocks and applying ternary search on them.

This is the only thing related to the blog that I found. I tried googling and asking people involved in CP, but none of them knew about it before.

Testing

The function from the example is boring, so consider a more interesting function: Weierstrass function

We zoom in and find that the maximum is about $$$1.1628$$$

Spoiler

We will search for maximum on the interval (-1, 1).

Code

This gives us $$$1.07203$$$. Let's split arguments into blocks. Since the arguments are real, in fact we are not going to split them explicitly into blocks, we will take the minimum in some range near the argument.

Blocks

It gives $$$1.07228$$$, $$$1.07245$$$, $$$1.07457$$$, $$$1.07203$$$ with block number = $$$2e4$$$, $$$2e5$$$, $$$2e6$$$, $$$2e7$$$, respectively. Not a good improvement, and somehow the result for the maximum number of blocks is the worst.

Теги ternary search, optimisation

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en8 Английский polosatic 2024-11-04 15:28:13 26 Tiny change: 'y approach.\n\nFrom ' -> 'y approach (the fourth optimisation).\n\nFrom '
ru1 Русский polosatic 2024-11-04 15:27:30 7676 Первая редакция перевода на Русский
en7 Английский polosatic 2024-11-04 15:24:31 40
en6 Английский polosatic 2024-11-04 15:23:43 24
en5 Английский polosatic 2024-11-04 14:59:46 6 Tiny change: 'nction is often close to ' -> 'nction is close to '
en4 Английский polosatic 2024-11-04 14:54:37 190 Tiny change: 'isation\nI should mention [this bl' -> 'isation\nIt is mentioned in [this bl' (published)
en3 Английский polosatic 2024-11-04 14:22:09 2719 Tiny change: 's, C));`\nIt gives' -> 's, C));`\n\nIt gives'
en2 Английский polosatic 2024-11-04 12:50:52 1362 Tiny change: 'the worst.' -> 'the worst.\n\nThe third optimisation ------------------'
en1 Английский polosatic 2024-11-04 12:09:48 3911 Initial revision (saved to drafts)