import sys,os
import numpy as np
import time

jcm_optimizer_path = r"<JCM_OPTIMIZER_PATH>"
sys.path.insert(0, os.path.join(jcm_optimizer_path, "interface", "python"))
from jcmoptimizer import Server, Client, Study, Observation
server = Server()
client = Client(server.host)

# Definition of the search domain
design_space = [
    {'name': 'x1', 'type': 'continuous', 'domain': (-1.5,1.5)}, 
    {'name': 'x2', 'type': 'continuous', 'domain': (-1.5,1.5)},
]

# Definition of fixed environment parameter
environment = [
    {'name': 'radius', 'type': 'fixed', 'domain': 1.5},
]

# Definition of a constraint on the search domain
constraints = [
    {'name': 'circle', 'expression': 'sqrt(x1^2 + x2^2) <= radius'}
]

# Creation of the study object with study_id 'scipy_minimization'
study = client.create_study(
    design_space=design_space,
    environment=environment,
    constraints=constraints,
    driver="ScipyMinimizer",
    name="Gradient-based minimization of a non-expensive scalar function",
    study_id="scipy_minimization"
)

# Configure study parameters
study.configure(max_iter=80, num_parallel=2, num_initial=6, jac=True, method="SLSQP")

# Evaluation of the black-box function for specified design parameters
def evaluate(study: Study, x1: float, x2: float, radius: float) -> Observation:

    observation = study.new_observation()
    observation.add(10*2
                + (x1**2-10*np.cos(2*np.pi*x1)) 
                + (x2**2-10*np.cos(2*np.pi*x2))
            )
    observation.add(2*x1+2*np.pi*np.sin(2*np.pi*x1),
                    derivative="x1")
    observation.add(2*x2+2*np.pi*np.sin(2*np.pi*x2),
                    derivative="x2")
    return observation

# Run the minimization
study.set_evaluator(evaluate)
study.run()
best = study.driver.best_sample
print(f"Best sample at: x1={best['x1']:.3f}, x2={best['x2']:.3f}")
