# probeOptimalColors: compute optimal colors by ray tracing In colorSpec: Color Calculations with Emphasis on Spectral Data

## Description

Consider a colorSpec object `x` with `type` equal to `'responsivity.material'`. The set of all possible material reflectance functions (or transmittance functions) is convex, closed, and bounded (in any reasonable function space), and this implies that the set of all possible output responses from `x` is also convex, closed, and bounded. The latter set is called the object-color solid or Rösch Farbkörper for `x`. A color on the boundary of the object-color solid is called an optimal color. The special points W (the response to the perfect reflecting diffuser) and 0 are on the boundary of this set. The interior of the line segment of neutrals joining 0 to W is in the interior of the object-color solid. It is natural to parameterize this segment from 0 to 1 (from 0 to W).

A ray r that is based at a point on the interior of the neutral line segment must intersect the boundary of the object-color solid in a unique optimal color. The purpose of the function `probeOptimalColors()` is to compute that intersection point.

The function only works as stated if:

1. the number of spectra in `x` is 3 (e.g. RGB or XYZ)

2. the chromaticity diagram of `x` is convex and well-ordered (injective with no reversals)

The 1st condition makes the situation simple enough to deal with. The 2nd condition implies that a reflectance function is optimal iff it takes the values 0 or 1, and has 0, 1, or 2 transitions; see Logvinenko or West for the proof of this. The proof in Schrödinger is flawed because he ignored condition 2. This 2-transition condition also simplifies the situation. Fortunately, the CIE chromaticity diagrams (both 1931 and 1964) are convex and injective. For counter-examples see the References. If we define a Schrödinger object color to be a color defined by a reflectance function with 0, 1, or 2 transitions, then it would be accurate to state that `probeOptimalColors()` computes Schrödinger object colors.

## Usage

 ```1 2``` ```## S3 method for class 'colorSpec' probeOptimalColors( x, gray, direction, tol=1.e-6, aux=FALSE ) ```

## Arguments

 `x` a colorSpec object with `type` equal to `'responsivity.material'` and 3 spectra `gray` vector of numbers in the open interval (0,1) that define neutral grays on the line segment from black to white; this neutral gray point is the basepoint of a probe ray `direction` a numeric Nx3 matrix with directions of the probe rays in the rows, or a numeric vector that can be converted to such a matrix, by row. `tol` error tolerance for the intersection of probe and object-color boundary `aux` a logical that specifies whether to return extra performance data; see Details

## Details

Each gray level and each direction defines a ray. So the total number of rays traced is `length(gray)` * `nrow(direction)`. The intersection problem is reduced to a 2-dimension root finding problem which is solved using Newton's Method. The initial estimate is found by precomputing a fine quadrilateral mesh over the optimal colors.
The responsivity functions may be negative, but may not all simultaneously vanish at any wavelength. In that case the mapping from the ω and δ sphere to the output response space is not injective.

## Value

`probeOptimalColors()` returns a `data.frame` with a row for each traced ray. There are `length(gray)` * `nrow(direction)` rays. The columns in the output are:

 `gray` the graylevel defining the basepoint of the ray. basepoint = gray*W `direction` the direction of the ray `s` computed scalar so that basepoint + s*direction is optimal `optimal` the optimal color on the boundary; optimal = basepoint + s*direction `lambda` lambda.1 and lambda.2 at the 2 transitions, in nm. lambda.1 < lambda.2 => bandpass, and lambda.1 > lambda.2 => bandstop. `dol` `delta` and `omega` - the Logvinenko parameters (δ,ω) for optimal colors, plus `lambda` in nm. ω is the reparameterization of λ ; see References

And if aux is `TRUE`, these auxiliary columns related to performance:

 `time_grid` time to find initial estimate point on boundary, in seconds `iters` number of interations of Newton's Method to find the ray intersection `btracks` total # of backtracks in "damped" Newton's method `time_newt` time spent in Newton iterations, in seconds `error` root-finding error, in coordinates of the optimal color. Always less than argument `tol`

If an individual ray could not be traced (see Known Issues), the row contains `NA` in appropriate columns.
In case of global error, the function returns `NULL`.

## Known Issues

The optimal color boundary is not differentiable at 0 and W. There may be numerical iteration failures near these 2 points.

## References

Logvinenko, A. D. An object-color space. Journal of Vision. 9(11):5, 1-23, (2009). https://jov.arvojournals.org/article.aspx?articleid=2203976. doi:10.1167/9.11.5.

Schrödinger, E. (1920). Theorie der Pigmente von grösster Leuchtkraft. Annalen der Physik. 62, 603-622.

West, G. and M. H. Brill. Conditions under which Schrödinger object colors are optimal. Journal of the Optical Society of America. 73. pp. 1223-1225. 1983.

`type`, vignette optimals