1# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
5
6import os
7from unittest import SkipTest
8import warnings
9import typing as tp
10import pytest
11import numpy as np
12from nevergrad.common import testing
13from . import core
14from . import photonics
15
16
17@testing.parametrized(
18    bragg=("bragg", [2.93, 2.18, 2.35, 2.12, 45.77, 37.99, 143.34, 126.55]),
19    morpho=("morpho", [280.36, 52.96, 208.16, 72.69, 89.92, 60.37, 226.69, 193.11]),
20    chirped=("chirped", [170.18, 56.48, 82.04, 48.17, 45.77, 37.99, 143.34, 126.55]),
21)
22def test_photonics_bounding_methods(pb: str, expected: tp.List[float]) -> None:
23    func = core.Photonics(pb, 8, bounding_method="tanh")
24    np.random.seed(24)
25    x = np.random.normal(0, 1, size=8)
26    output = func.parametrization.spawn_child().set_standardized_data(x).value.ravel()
27    np.testing.assert_almost_equal(output, expected, decimal=2)
28
29
30@testing.parametrized(
31    # bragg domain (n=60): [2,3]^30 x [30,180]^30
32    bragg=("bragg", [2.5, 2.5, 2.5, 2.5, 105.0, 105.0, 105.0, 105.0]),
33    # chirped domain (n=60): [30,170]^60
34    chirped=("chirped", [105.0, 105.0, 105.0, 105.0, 105.0, 105.0, 105.0, 105.0]),
35    # morpho domain (n=60): [0,300]^15 x [0,600]^15 x [30,600]^15 x [0,300]^15
36    morpho=("morpho", [150.0, 150.0, 300.0, 300.0, 315.0, 315.0, 150.0, 150.0]),
37)
38def test_photonics_mean(pb: str, expected: tp.List[float]) -> None:
39    func = core.Photonics(pb, 8)
40    all_x = func.parametrization.value
41    output = all_x.ravel()
42    np.testing.assert_almost_equal(output, expected, decimal=2)
43
44
45def test_morpho_bounding_method_constraints() -> None:
46    func = core.Photonics("morpho", 60, bounding_method="arctan")
47    x = np.random.normal(0, 5, size=60)  # std 5 to play with boundaries
48    output1 = func.parametrization.spawn_child().set_standardized_data(x)
49    output2 = output1.sample()
50    for output in (output1, output2):
51        assert np.all(output.value >= 0)
52        assert np.all(output.value[[0, 3], :] <= 300)
53        assert np.all(output.value[[1, 2], :] <= 600)
54        assert np.all(output.value[2, :] >= 30)
55
56
57def test_photonics_bragg_recombination() -> None:
58    func = core.Photonics("bragg", 8)
59    # func.parametrization.set_recombination(ng.p.mutation.RavelCrossover())  # type: ignore
60    func.parametrization.random_state.seed(24)
61    arrays = [func.parametrization.spawn_child() for _ in range(2)]
62    arrays[0].value = [[2, 2, 2, 2], [35, 35, 35, 35]]
63    arrays[1].value = [[3, 3, 3, 3], [45, 45, 45, 45]]
64    arrays[0].recombine(arrays[1])
65    expected = [[3, 2, 2, 2], [45, 35, 35, 35]]
66    np.testing.assert_array_equal(arrays[0].value, expected)
67
68
69def test_photonics_custom_mutation() -> None:
70    func = core.Photonics("morpho", 16, rolling=True)
71    param = func.parametrization.spawn_child()
72    for _ in range(10):
73        param.mutate()
74    # as tuple
75    func = core.Photonics("morpho", 16, rolling=True, as_tuple=True)
76    func.evaluation_function(func.parametrization)
77
78
79def test_photonics_error() -> None:
80    # check error
81    photo = core.Photonics("bragg", 16)
82    np.testing.assert_raises(AssertionError, photo, np.zeros(12))
83    with warnings.catch_warnings(record=True) as ws:
84        output = photo(np.zeros(16))
85        # one warning on Ubuntu, two warnings with Windows
86        assert any(isinstance(w.message, RuntimeWarning) for w in ws)
87    np.testing.assert_almost_equal(output, float("inf"))
88
89
90@pytest.mark.parametrize("method", ["clipping", "arctan", "tanh", "constraint"])  # type: ignore
91@pytest.mark.parametrize("name", ["bragg", "morpho", "chirped"])  # type: ignore
92def test_no_warning(name: str, method: str) -> None:
93    with warnings.catch_warnings(record=True) as w:
94        core.Photonics(name, 24, bounding_method=method)
95        assert not w, f"Got a warning at initialization: {w[0]}"
96
97
98@testing.parametrized(
99    # morpho=("morpho", 100, 1.1647),  # too slow
100    chirped=("chirped", 150, 0.94439),
101    bragg=("bragg", 2.5, 0.93216),
102)
103def test_photonics_values(name: str, value: float, expected: float) -> None:
104    if name == "morpho" and os.environ.get("CIRCLECI", False):
105        raise SkipTest("Too slow in CircleCI")
106    photo = core.Photonics(name, 16)
107    np.testing.assert_almost_equal(photo(value * np.ones(16)), expected, decimal=4)
108
109
110GOOD_CHIRPED = [
111    89.04887416,
112    109.54188095,
113    89.74520725,
114    121.81700431,
115    179.99830918,
116    124.38222473,
117    95.31017129,
118    116.0239629,
119    92.92345776,
120    118.06108198,
121    179.99965859,
122    116.89288181,
123    88.90191494,
124    110.30816229,
125    93.11974992,
126    137.42629858,
127    118.81810084,
128    110.74139708,
129    85.15270955,
130    100.9382438,
131    81.44070951,
132    100.6382896,
133    84.97336252,
134    110.59252719,
135    134.89164276,
136    121.84205195,
137    89.28450356,
138    106.72776991,
139    85.77168797,
140    102.33562547,
141]
142
143
144EPS_AND_D = [  # photosic realistic
145    2.0000,
146    3.0000,
147    2.1076,
148    2.0000,
149    3.0000,
150    2.5783,
151    2.0000,
152    3.0000,
153    2.0000,
154    3.0000,
155    90.0231,
156    78.9789,
157    72.8369,
158    99.9577,
159    82.7487,
160    62.7583,
161    104.1682,
162    139.9002,
163    93.3356,
164    75.6039,
165]
166
167
168@testing.parametrized(
169    morpho=("morpho", 1.127904, None),
170    chirped=("chirped", 0.594587, None),
171    good_chirped=("chirped", 0.275923, np.array([GOOD_CHIRPED])),  # supposed to be better
172    bragg=("bragg", 0.96776, None),
173    photosic_realistic=("cf_photosic_realistic", 0.0860257, np.array(EPS_AND_D).reshape((2, -1))),
174    photosic_reference=("cf_photosic_reference", 0.431072, None),
175)
176def test_photonics_values_random(name: str, expected: float, data: tp.Optional[np.ndarray]) -> None:
177    if name == "morpho" and os.environ.get("CIRCLECI", False):
178        raise SkipTest("Too slow in CircleCI")
179    size = data.size if data is not None else (16 if name != "morpho" else 4)
180    photo = core.Photonics(name, size)
181    if data is None:
182        x = np.random.RandomState(12).normal(0, 1, size=size)
183        candidate = photo.parametrization.spawn_child().set_standardized_data(x)
184    else:
185        candidate = photo.parametrization.spawn_child(new_value=data)
186    np.testing.assert_almost_equal(photo(candidate.value), expected, decimal=4)
187    np.testing.assert_almost_equal(photo.evaluation_function(candidate), expected, decimal=4)
188
189
190def test_photosic_reference() -> None:
191    debut = np.array([79, 102])
192    fin = np.array([100, 70])
193    dbr = np.tile(np.array([147, 120]), 3)
194    X = np.concatenate([debut, dbr, fin])
195    cf_test = photonics.cf_photosic_reference(X)
196    np.testing.assert_almost_equal(cf_test, 0.09069397)
197
198
199def test_photosic_realist() -> None:
200    cf_test = photonics.cf_photosic_realistic(np.array(EPS_AND_D))
201    np.testing.assert_almost_equal(cf_test, 0.08602574254532869)
202