Skip to content

Commit ee81e0f

Browse files
author
Weslley da Silva Pereira
committed
Enhance documentation standards and improve docstring formats across multiple files
1 parent c4d6aa0 commit ee81e0f

File tree

10 files changed

+206
-35
lines changed

10 files changed

+206
-35
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ repos:
1919
- id: check-merge-conflict
2020

2121
- repo: https://github.com/astral-sh/ruff-pre-commit
22-
rev: v0.8.4
22+
rev: v0.12.7
2323
hooks:
2424
- id: ruff
2525
args: [--fix, --exit-non-zero-on-fix]

CONTRIBUTING.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,135 @@ __authors__ = ["Your Name"]
5050
```
5151

5252
For files based on existing algorithms from academic papers, include the original authors in the `__credits__` variable at [soogo/\_\_init\_\_.py](soogo/__init__.py).
53+
54+
## Documentation standards
55+
56+
### Docstring format
57+
58+
This project uses **Sphinx-style** docstrings with reStructuredText (reST) markup. All public modules, classes, methods, and functions must have docstrings.
59+
60+
#### Module docstrings
61+
62+
Every Python module should start with a brief one-line description:
63+
64+
```python
65+
"""Brief description of the module."""
66+
```
67+
68+
#### Class docstrings
69+
70+
Classes should document their purpose, attributes, and parameters:
71+
72+
```python
73+
class MyClass:
74+
"""Brief one-line description.
75+
76+
Longer description if needed. Can span multiple paragraphs.
77+
78+
:param param1: Description of param1.
79+
:param param2: Description of param2.
80+
81+
.. attribute:: attr1
82+
83+
Description of attribute attr1.
84+
85+
.. attribute:: attr2
86+
87+
Description of attribute attr2.
88+
89+
References
90+
----------
91+
.. [#] Author. Title. Journal, Year. DOI or URL.
92+
"""
93+
```
94+
95+
#### Function docstrings
96+
97+
Functions should document parameters, return values, and include examples when helpful:
98+
99+
```python
100+
def my_function(x, y, *, option=None):
101+
"""Brief one-line description.
102+
103+
Longer description if needed. Explain what the function does,
104+
any important algorithms, or behaviors.
105+
106+
:param x: Description of parameter x.
107+
:param y: Description of parameter y.
108+
:param option: Description of optional keyword argument.
109+
The default is None.
110+
:return: Description of return value.
111+
112+
References
113+
----------
114+
.. [#] Author. Title. Journal, Year. DOI or URL.
115+
"""
116+
```
117+
118+
#### Key formatting rules
119+
120+
- **Line length**: Docstrings must not exceed 80 characters per line (enforced by Ruff W505)
121+
- **Parameters**: Use `:param name: description` format
122+
- **Return values**: Use `:return: description` format
123+
- **Types**: Can be included inline with params (e.g., `:param int maxeval:`) or use type hints
124+
- **Attributes**: Use `.. attribute:: name` followed by indented description
125+
- **References**: Use numbered footnote style with `.. [#]` for academic citations
126+
- **Math**: Use inline math with `:math:\`expression\``or display math blocks with`.. math::`
127+
128+
#### Example from the codebase
129+
130+
```python
131+
def gosac(
132+
fun,
133+
gfun,
134+
bounds,
135+
maxeval: int,
136+
*,
137+
surrogateModel: Optional[Surrogate] = None,
138+
disp: bool = False,
139+
callback: Optional[Callable[[OptimizeResult], None]] = None,
140+
):
141+
"""Minimize a scalar function subject to constraints.
142+
143+
The surrogate models are used to approximate the constraints. The
144+
objective function is assumed to be cheap to evaluate, while the
145+
constraints are assumed to be expensive to evaluate.
146+
147+
This method is based on [#]_.
148+
149+
:param fun: The objective function to be minimized.
150+
:param gfun: The constraint function to be minimized. The
151+
constraints must be formulated as g(x) <= 0.
152+
:param bounds: List with the limits [x_min,x_max] of each
153+
direction x in the search space.
154+
:param maxeval: Maximum number of function evaluations.
155+
:param surrogateModel: Surrogate model to be used for the
156+
constraints. If None is provided, a :class:`RbfModel` model
157+
is used.
158+
:param disp: If True, print information about the optimization
159+
process. The default is False.
160+
:param callback: If provided, the callback function will be
161+
called after each iteration with the current optimization
162+
result. The default is None.
163+
:return: The optimization result.
164+
165+
References
166+
----------
167+
.. [#] Juliane Müller and Joshua D. Woodbury. 2017. GOSAC: global
168+
optimization with surrogate approximation of constraints.
169+
J. of Global Optimization 69, 1 (September 2017), 117–136.
170+
https://doi.org/10.1007/s10898-017-0496-y
171+
"""
172+
```
173+
174+
### Building documentation
175+
176+
To build the HTML documentation locally:
177+
178+
```bash
179+
cd docs
180+
pip install -r requirements.txt
181+
sphinx-build . _build/html
182+
```
183+
184+
The generated documentation will be in `docs/_build/html/`.

soogo/acquisition/maximize_distance.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def optimize(
6464
:param n: Number of points to be acquired.
6565
:param points: Points to consider for distance maximization. If None,
6666
use all previously sampled points in the surrogate model.
67+
:return: Array of acquired points that maximize minimum distance.
6768
"""
6869
iindex = surrogateModel.iindex
6970
optimizer = self.optimizer if len(iindex) == 0 else self.mi_optimizer

soogo/acquisition/multiple_acquisition.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@
2626

2727

2828
class MultipleAcquisition(Acquisition):
29+
r"""Apply multiple acquisition functions sequentially.
30+
31+
This acquisition function runs multiple acquisition strategies in
32+
sequence, filtering candidates to ensure they are far enough apart.
33+
34+
:param acquisitionFuncArray: Sequence of acquisition functions to
35+
apply in order.
36+
:param **kwargs: Additional arguments passed to the base Acquisition
37+
class.
38+
"""
39+
2940
def __init__(
3041
self,
3142
acquisitionFuncArray: Sequence[Acquisition],

soogo/acquisition/utils.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import numpy as np
2222
from scipy.spatial.distance import cdist
23-
from scipy.spatial import KDTree
2423
import networkx as nx
2524
from networkx.algorithms.approximation import maximum_independent_set
2625

@@ -152,13 +151,10 @@ def select_weighted_candidates(
152151
:param tol: Tolerance value for excluding candidates that are too close to
153152
current sample points.
154153
:param weightpattern: List of weights to use cyclically for each selection.
155-
:return:
156-
157-
* n-by-dim matrix with the selected points.
158-
159-
* n-by-(n+m) matrix with the distances between the n selected points
160-
and the (n+m) sampled points (m is the number of points that have
161-
been sampled so far).
154+
:return: Tuple containing (1) n-by-dim matrix with the selected
155+
points, and (2) n-by-(n+m) matrix with the distances between the
156+
n selected points and the (n+m) sampled points (m is the number
157+
of points that have been sampled so far).
162158
"""
163159
# Compute neighbor distances
164160
dist = np.min(distx, axis=1)
@@ -224,15 +220,32 @@ def select_weighted_candidates(
224220

225221

226222
class FarEnoughSampleFilter:
227-
def __init__(self, X, tol):
228-
self.tree = KDTree(X)
229-
self.tol = tol
223+
"""Filter candidate points that are too close to existing points.
224+
225+
This utility class filters out candidates that are within a minimum
226+
distance threshold from already sampled points.
227+
228+
:param X: Matrix of existing sample points (n x d).
229+
:param tol: Minimum distance threshold. Points closer than this are
230+
filtered out.
231+
"""
230232

231233
def is_far_enough(self, x):
234+
"""Check if a point is far enough from existing samples.
235+
236+
:param x: Point to check (d-dimensional vector).
237+
:return: True if the point is far enough, False otherwise.
238+
"""
232239
dist, _ = self.tree.query(x.reshape(1, -1))
233240
return dist[0] >= self.tol
234241

235242
def __call__(self, Xc):
243+
"""Filter candidates based on minimum distance criterion.
244+
245+
:param Xc: Matrix of candidate points (m x d).
246+
:return: Filtered matrix containing only points that are far
247+
enough from existing samples.
248+
"""
236249
# Discard points that are too close to X
237250
mask0 = np.array([self.is_far_enough(x) for x in Xc], dtype=bool)
238251
Xc0 = Xc[mask0]

soogo/model/rbf.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,14 @@ def reserve(self, maxeval: int, dim: int, ntarget: int = 1) -> None:
202202
)
203203

204204
def eval_kernel(self, x, y=None):
205+
"""Evaluate the RBF kernel between points.
206+
207+
:param x: m-by-d matrix with m point coordinates in a d-dimensional
208+
space.
209+
:param y: n-by-d matrix with n point coordinates in a d-dimensional
210+
space. If None, use x.
211+
:return: Kernel matrix (m x n).
212+
"""
205213
if y is None:
206214
y = x
207215
return self.rbf(cdist(x, y))
@@ -283,9 +291,9 @@ def __call__(
283291
coef0 = self._coef[0 : self._m]
284292
coef1 = self._coef[self._m : self._m + self.polynomial_tail_size()]
285293
if i >= 0:
286-
assert (
287-
i < self.ntarget
288-
), "Index out of bounds for target dimension."
294+
assert i < self.ntarget, (
295+
"Index out of bounds for target dimension."
296+
)
289297
if self.ntarget > 1:
290298
coef0 = coef0[:, i]
291299
coef1 = coef1[:, i]
@@ -525,7 +533,7 @@ def iindex(self) -> tuple[int, ...]:
525533
"""Return iindex, the sequence of integer variable indexes."""
526534
return self._iindex
527535

528-
def prepare_mu_measure(self):
536+
def prepare_mu_measure(self) -> None:
529537
"""Prepare the model for mu measure computation.
530538
531539
This routine computes the LDLt factorization of the matrix A, which is

soogo/optimize/bayesian_optimization.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ def bayesian_optimization(
3030
acquisitionFunc: Optional[MaximizeEI] = None,
3131
**kwargs,
3232
) -> OptimizeResult:
33-
"""Wrapper for surrogate_optimization() using a Gaussian Process surrogate
33+
r"""Wrapper for surrogate_optimization() using a Gaussian Process surrogate
3434
model and the Expected Improvement acquisition function.
3535
36-
:param \\*args: Positional arguments passed to surrogate_optimization().
36+
:param *args: Positional arguments passed to surrogate_optimization().
3737
:param surrogateModel: Gaussian Process surrogate model. The
3838
default is GaussianProcess(). On exit, if provided, the surrogate
3939
model the points used during the optimization.
4040
:param acquisitionFunc: Acquisition function to be used.
41-
:param \\*\\*kwargs: Keyword arguments passed to surrogate_optimization().
41+
:param **kwargs: Keyword arguments passed to surrogate_optimization().
4242
"""
4343
# Initialize optional variables
4444
if surrogateModel is None:

soogo/sampling.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ class SamplingStrategy(Enum):
3939
MITCHEL91 = 6 #: Cover empty regions in the search space
4040

4141

42-
def _slhd_permutation_matrix(m: int, d: int):
42+
def _slhd_permutation_matrix(m: int, d: int) -> np.ndarray:
43+
"""Generate permutation matrix for SLHD sampling.
44+
45+
:param m: Number of samples.
46+
:param d: Number of dimensions.
47+
:return: m-by-d permutation matrix.
48+
"""
4349
# Generate permutation matrix P
4450
P = np.zeros((m, d), dtype=int)
4551
P[:, 0] = np.arange(m)

soogo/termination.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@ class TerminationCondition(ABC):
3636
3737
This class defines the interface for conditions that can be used to
3838
determine when an optimization process should terminate.
39-
40-
:param out: The optimization result containing the current state.
41-
:param model: The surrogate model used in the optimization, if any.
42-
:return: True if the condition is met, False otherwise.
4339
"""
4440

4541
@abstractmethod
4642
def is_met(self) -> bool:
47-
"""Check if the condition is met."""
43+
"""Check if the termination condition is met.
44+
45+
:return: True if the condition is met, False otherwise.
46+
"""
4847
pass
4948

5049
@abstractmethod
@@ -104,19 +103,19 @@ def update(
104103
# No function evaluations, cannot update condition
105104
return
106105

107-
assert (
108-
out.nobj == 1
109-
), "Expected a single objective function value, but got multiple objectives."
106+
assert out.nobj == 1, (
107+
"Expected a single objective function value, but got multiple objectives."
108+
)
110109

111110
# Get the new best value from the optimization result
112111
new_best_value = (
113112
out.fx.flatten()[0].item()
114113
if isinstance(out.fx, np.ndarray)
115114
else out.fx
116115
)
117-
assert isinstance(
118-
new_best_value, float
119-
), "Expected out.fx to be a float, but got a different type."
116+
assert isinstance(new_best_value, float), (
117+
"Expected out.fx to be a float, but got a different type."
118+
)
120119

121120
# Compute the relative improvement
122121
value_improvement = self.lowest_value - new_best_value
@@ -148,9 +147,9 @@ def __init__(self, termination: TerminationCondition, period=30) -> None:
148147
self.history = deque(maxlen=period)
149148

150149
def is_met(self) -> bool:
151-
assert isinstance(
152-
self.history.maxlen, int
153-
), "History maxlen must be set."
150+
assert isinstance(self.history.maxlen, int), (
151+
"History maxlen must be set."
152+
)
154153

155154
if len(self.history) < self.history.maxlen:
156155
return False

soogo/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def find_pareto_front(fx, iStart: int = 0) -> list:
3636
elif all(fx[j] <= fx[i]) and any(fx[j] < fx[i]):
3737
# x[j] dominates x[i]
3838
# No need to continue checking, otherwise the previous
39-
# iteration was not a balid Pareto front
39+
# iteration was not a valid Pareto front
4040
pareto[i] = False
4141
break
4242
return [i for i in range(len(fx)) if pareto[i]]
@@ -49,6 +49,7 @@ def gp_expected_improvement(delta, sigma):
4949
the current best function value and :math:`\\mu_n(x)` is the expected
5050
value for :math:`f(x)`.
5151
:param sigma: The standard deviation :math:`\\sigma_n(x)`.
52+
:return: Expected improvement value.
5253
5354
References
5455
----------

0 commit comments

Comments
 (0)