Changelog¶
The list of what changed in each version of Dense-Armor: new features and fixes.
Included directly from the repository CHANGELOG.md
so it never goes stale relative to the single source of truth.
Changelog¶
Formato basato su Keep a Changelog.
[Unreleased]¶
[1.5.0] - 2026-11-07¶
Changed¶
- The library is float32 by default.
dense_armor.set_default_dtype("float64")andget_default_dtype()change it. The classes that build numpy arrays (the vision, the anomaly scorers, the CUSUM detector,Orca, the arbiter, the continual-learning and consolidation classes, the similarity index, the MCP tools) use it.core/engine.pystays float64: it runs its computations inside a local x64 context, so the rest of the process is not forced to x64. Thedynamicsmodules and the MCP server no longer turn onjax_enable_x64for the whole process on import. Left in float64:Armorandhybrid_engine(below about 1e-38 float32 loses the drift: measured),BayesianLinearRegressionand the Mondrian forests (their float32 audit failed: negative variance, copiedM2),BetaandMultinomial(prudential, not measured: float32 counters lose exactness above 2^24),Metro,gradcheck, and thedynamicsmodules (RigidBodyModeland the two passivity controllers: the QP goes to OSQP in float64; the drift offorward_dynamicsin float32 is not measured).
Added¶
dense_armor.utility.finance, four building blocks for trading systems, each with tests, a benchmark and a page underdocs/finance/:twap_scheduleandvwap_schedule;PreTradeCheckerandDecision, the five controls of SEC Rule 15c3-5 (c)(1) (size, price band, duplicate, rate, notional);decode_message, for the seven Nasdaq TotalView-ITCH 5.0 order messages (A,F,E,C,X,D,U);RingBuffer, with the storage allocated once at construction.
Fixed¶
ResidualTree.consolidate()raisedTypeError: dot_general requires contracting dimensions to have the same shapeford_model >= 5: it passed the codebooks of the children, which live in latent space, toVQMemory.fit, which expects data in input space. It now decodes the codebooks of the children, runs k-means down ton_codes // branchingcentroids, rebuilds the parentVQMemoryfrom them and fits it for 5 steps.
[1.4.0] - 2026-11-02¶
Changed¶
core/engine.pyno longer asks JAX forfloat64: it computes in the default dtype of JAX, sofloat32as it comes, andfloat64when the user setsjax_enable_x64. Before, the explicit request was silently cut tofloat32with aUserWarningunless x64 was on. The arrays returned byAdaptiveSignalStabilizerfollow the same dtype.AdaptiveConformalRegressor.budget_sandmemory_classare now properties that read the wrapped model.budget_ssums two model calls (predict_one and learn_one) plus 5e-4, and isNoneif the model declares no budget;memory_classis the larger of the model class and O(window),unknownif the model declares something else. Withbudget_sNone,RealtimePipelinewithrequire_budget=Truerefuses the wrapper._Pipelineforwardstto everytransform_onein_forward,learn_one,transform_one,predict_one,predict_proba_one, not just tolearn_one.SignalBatchwith zero rows hast = Noneinstead of raisingIndexError.AnomalyDetectordeclaresscore_is_inverted = Falseon the base class; a subclass whosescore_oneis not "high = anomalous" must set it toTrue(see theChauvenetScorerentry under Fixed).- New check
check_clone_drops_learned_state:clone()must not carry learned state. Can makecheck_estimatorfail on third-party estimators whose constructor is not rebuildable fromget_params(reported as not applicable, whichcheck_estimatorturns into anAssertionErrorunless the check is inallow_not_applicableor in_unit_test_skips) or whoseclone()copies learned state (AssertionError). Protectedwith a drift detector reads the flag from the lastlearn_one, so the firstfallbackis returned on the sample after the one that triggered drift. Documented in the class docstring and enforced bytest_predict_after_drift_returns_the_fallback_on_the_next_sample.Armaturais nowArmor, the moduledense_armor/armatura.pyis nowdense_armor/armor.py, and the docs pageshields/armatura.mdis nowshields/armor.md. The old import pathdense_armor.armaturastill works (it resolves todense_armor.armor) anddense_armor.Armaturastill works and raises aDeprecationWarning. The console scriptdense-armoris unchanged. The entries below keep the old name.- The 33 folders of
dense_armor/utilitysit in six groups:online,deep(neural),signals,robot(control),evaluationandinfrastructure. No file changed inside the folders. The old paths, for exampledense_armor.utility.bandit.ucb, still work and resolve to the same module object asdense_armor.utility.online.bandit.ucb. The entries below keep the old paths. - The
miscfolder is dissolved:SDFTandSkylineare inutility/signals/stats,Metroinutility/signals/preprocessing,diaginutility/evaluation/evaluateandlodatinutility/signals/stream. There are 32 subpackages in six groups. The old paths (dense_armor.utility.misc.sdft,dense_armor.utility.infrastructure.misc.metro, ...) still resolve to the same modules, and the old packagemiscgivesSDFTandSkyline. The pagedocs/misc/skyline.mdis nowdocs/stats/skyline.md; the tests moved with the modules. -
Incompatible with 1.2.0:
GatedCrossAttentionreturnsg * attention(xq, xc)again, as the item B of the task asked, and notxq + g * attention(xq, xc). A closed step produces the zero vector and sends no gradient to the queries, to the context or to a parameter. The form of 1.2.0 is gone. To keep the static part of the query, add it outside of the class withResidualorParallel; the pagedocs/neural/composable_attention.mdshows it. -
Incompatible:
Signaltakes only a vector of shape(n_channels,); a matrix raisesValueError. Several samples are aSignalBatch, andSignal.stackedreturns one. The batch form ofSignalwas a dict whose values were tuples, which no estimator of the library accepts, and its docstring said that any estimator took it.
Added¶
SignalBatch(indense_armor.roles):nsamples ofdchannels for the code that works onarray(shape(n, d)). It is not a dict;row(i)gives the sampleias aSignal;timesandtcarry the timestamps.budget_sandmemory_classof the wrappers:AnomalyGate,Protected,SafeEstimator,UnitCheckedPipeline, the|pipeline and the union and product of transformers declare the cost of their parts (Noneif a part declares none), so thatRealtimePipelinetakes them as a step and checks their real cost. Before, none declared a budget andRealtimePipelinerefused every wrapper.dense_armor/utility/deep/neural/rotary_decoder.py:RotaryDecoderBlock,RotaryDecoderand the builderrotary_decoder, a causal language model with RMSNorm, rotary causal attention and a SwiGLU feed-forward (pre-norm), withremat=Trueto recompute the activations inbackward.transformer_decoderis unchanged.MultiHeadSelfAttention.init_static_cacheandstep_static: one token against a key/value cache of fixed shape, so the step compiles once.generateuses it for a model that hasinit_static_cacheandstep_compiled(RotaryDecoder); any other model is run again on the whole prefix, as before.benchmarks/neural/kv_cache.pymeasures the two paths; pagedocs/neural/rotary_decoder.md.benchmarks/train_small_lm.py, its two GPU JSONs and the pagedocs/neural/lm_training.md: a 14.2-million-parameter decoder trained on tiny Shakespeare on a Tesla T4, with the unigram and bigram baselines.
Fixed¶
ChauvenetScorerdeclaresscore_is_inverted = True;AnomalyGateandProtectedraiseValueErrorwhen the detector declares that attribute, because itsscore_oneis inverted with respect to the "high score = anomalous" contract. Before, both gates classified inliers as anomalies. UseHampelScorer,SigmaClipScorerorTukeyScorerwith the gates, or callChauvenetScorer.is_outlierdirectly.rotary_anglesaccepts a tracedoffset(it built the positions withjnp.arange(offset, ...), which needs a concrete value, so a compiled step with a traced position failed).UnitCheckedPipeline.learn_onetrains every transformer of the pipeline, as|does. It only transformed with them, so an adaptive transformer never learned inside it.Transformer: the result ofa + banda * bis aTransformer, so(a + b) + c,(a * b) * canda + (b * c)work (they raisedAttributeError).AnomalyGatetakes athresholdargument, and raisesValueErrorfor a detector that has nothresholdattribute and no argument. The default of0.0classified as anomalous every positive score, and withprotect=Truethe detector stopped learning after the first sample.StreamingDeviationScorer,PatchMemoryandOnlineAutoencoderneedthreshold=.AdaptiveConformalRegressor.predict_one(return_std=True)and(return_estimate=True)give the root mean square of the residuals of the window, and its square as the variance. They gave the half-width of the conformal interval (a quantile, not a standard deviation) and its square.predict_intervalis unchanged.call_jsonalways writes valid JSON: a float that is not finite is the string"Infinity","-Infinity"or"NaN"(it wrote the constantsInfinityandNaN, which JSON does not have, for theinfscore of a flagged sample).Protectedfeeds a drift detector the value offeature, the smallest key when it isNone, and not the first key of the dict. New argumentfeature. A detector withscore_oneand athresholdattribute is applied (it was never flagged). New argumenttrain_detector(defaultFalse, behaviour unchanged): withTrue,learn_onetrains the detector on the samples that are not flagged; a detector that nobody trains flags nothing. A drift detector is fed bylearn_oneonly, once per sample:predict_oneandscore_oneupdated it too, so a sample that was predicted and then learned counted twice; they now read its current state.- Documented, no change of behaviour: the batch form of
Signalholds a tuple per channel and is not accepted bylearn_one;SafeEstimator.learn_onecosts apredict_oneand alearn_oneof the model. GatedCrossAttentionrefuses a signal of shape(Tq, Tq)for a 2-D query (it was accepted and gave an output of the wrong shape); the example ofsinusoidal_positionsno longer depends on the float type.
Removed¶
- Nothing yet.
[1.3.1] - 2026-10-30¶
Added¶
dense_armor/bridges/sklearn.py:SklearnClassifier,SklearnRegressor,SklearnTransformer(a Dense-Armor estimator behind the scikit-learn interface) andfrom_sklearn(a scikit-learn estimator behind the Dense-ArmorEstimatorinterface). The three wrappers passcheck_estimator27/27 with 0 not-applicable and 0 skipped on scikit-learn 1.6.1. New extrasklearn = ["scikit-learn>=1.6"]. Pagedocs/bridges/sklearn.md.dense_armor/bridges/torch.py:to_jax/to_torch(DLPack, no copy on a contiguous CPU tensor),TorchModule(a Dense-Armor model as atorch.nn.Module, backward throughjax.vjp),from_torch(atorch.nn.Moduleas an adapter withjax.custom_vjp). New extratorch = ["torch>=2.0"]. Benchmarkbenchmarks/bridge_torch.pyand pagedocs/bridges/torch.md.dense_armor/bridges/tensorflow.py:to_jax/to_tf(DLPack with the capsule),KerasLayer(a Dense-Armor model as atf.keras.layers.Layerthroughjax2tf.convert),from_keras(atf.keras.Modelas an adapter withjax.custom_vjpandtf.GradientTape). New extratensorflow = ["tensorflow-cpu>=2.16"]. Benchmarkbenchmarks/bridge_tensorflow.pyand pagedocs/bridges/tensorflow.md.docs/bridges/index.md: overview of the available bridges. New "Bridges" section in the nav.
Changed¶
dense_armor/__init__.py:__version__aligned to thepyproject.tomlversion (was 1.2.1, the last release is 1.3.0).CITATION.cff:versionaligned to thepyproject.tomlversion.
Reported¶
- In all three bridges the cross-framework forward costs more than the same model called in the source framework: PyTorch 53x, TensorFlow 53x, both declared in their pages with the JSON benchmark. The claim of a 20% overhead declared in the first commit of each task does not hold.
from_kerasreturns a 2D output and works on float32 only.- The TensorFlow tests are skipped in the CI of this branch (the extra is not installed); they pass locally and on Kaggle.
[1.3.0] - 2026-10-29¶
Added¶
dense_armor/utility/stats/stable.py:stable_varandstable_moments, a float32 variance kernel. First-element anchor shift (shifted-data algorithm with K = x[0], Healy 2022 Algorithm 2; Sterbenz exact on close values), two-term formula, and a compensated tree (binary merge ofTwoSum,log2(n)vectorised levels) ond**2. Exactly0.0on identical values up to1e38in eager and underjax.jit, on CPU and on GPU; below the baseline two-term formula on 3 of 4 datasets of the task in each combination (the two exceptions are D4 on CPU and D3 underjax.jiton GPU, declared). Complex input returnsE|z - mean(z)|**2, real. 69 tests, 99% coverage of the new file. Two JSON benchmarks (benchmarks/stable_variance_float32.jsonfor CPU,benchmarks/stable_variance_float32_gpu.jsonfor a T4 GPU), a pagedocs/stats/stable_variance.mdand a README paragraph. The speed is 3.59x the baseline formula on CPU (range [3.55, 4.60] over three sessions) and 2.24x on GPU (range [2.20, 2.27]).-
dense_armor/utility/stats/: an optionaldtype=Nonekeyword on the 11 estimators of the statistics batch (RunningMoments,EWStats,RunningMomentsVector,RunningCovariance,Autocorrelation,RunningCorrelation,RollingCorrelation,RollingCovariance,OnlineGaussianMixture,OnlineShrinkageCovariance,StudentT). The default keeps float64 bit for bit;dtype=numpy.float32makes the state float32, and inRunningMomentslearn_manyof a float32 block usesstable_moments. Every class passescheck_estimator27/27 with 0 not-applicable and 0 skip, in both dtypes.OnlineShrinkageCovariancenow inheritsTransformer(it had noget_params, socheck_estimatorcould not run on it);RunningCovariance._valuesandRollingCovariance._valuesno longer raiseKeyErroron a sample withoutx/y(pre-existing, it brokecheck_learn_one_does_not_modify_xon master). -
dense_armor/utility/stats/student_t.py,mixture.py,dense_armor/utility/timeseries/garch.py:StudentT,OnlineGaussianMixture,GARCH11. Three online models: a robust location-scale estimator (Student-t withnufixed, Liu & Rubin 1995's EM update ofnunot implemented and declared), a Gaussian mixture with diagonal covariance (Cappé & Moulines 2009 online EM, Gaussian form derived and declared), and GARCH(1,1) conditional variance (Csáji 2018 Eq. 1b, parameters fixed and declared). Three test files (25, 24, 19 tests, 68 total, all green).benchmarks/statistics_online.pywith four synthetic domains (robot, drone, finance, bimodal), a--robustflag, and a--crunchdaoblock that measures whether the three features add signal on CrunchDAO DataCrunch 2 data. Three docs pages:docs/stats/student_t.md,docs/stats/mixture.md,docs/timeseries/garch.md. The learning-rate schedules were measured (constant, sqrt, pow, adaptive) and none dominates: the adaptive schedule gives the wrong sign on the robot domain. No adaptive schedule is added to the library; the class docstring declares that a fixed learning rate converges to a neighbourhood of the MLE. dense_armor/utility/neural/moe.py,test_time.py:SparseMoEandTestTimeCompute.SparseMoEdispatches each token to its top-k experts with a capacity factor (Fedus et al. 2021, Section 2.2, page 6), so only the selected experts run, not alln_experts.forward_pureis pure and compiles underjax.jit,backwardisjax.vjpofforward_pure, the load-balancing loss is Fedus Eq. (4), page 7, and the routing is a segment cumsum on the top-k indices,O(n_tokens * top_k * log(n_tokens * top_k)), independent ofn_experts. Optional noisy gating (Shazeer et al. 2017, Eq. (4), page 4, fixed noise std). Two metrics afterforward:expert_usage()anddropped_fraction().TestTimeComputegeneratesn_candidatesnoisy copies and picks the one a verifier scores highest. Four verifiers:"confidence"(default),"margin","entropy","ensemble". Two test files (16 + 6 tests, 22 total, all green).benchmarks/neural/moe_testtime.pywith two synthetic 2D experiments (MoE vs dense, test-time vs single pass), 5 seeds, mean ± std, a--smokeflag; the JSON isbenchmarks/neural/moe_testtime.json. One docs page,docs/neural/moe_testtime.md, with the dispatch mask imagedocs/assets/moe_testtime/dispatch_mask.pnggenerated by the library. On the micro domain the MoE beats the dense on task 1 (0.722 ± 0.077 vs 0.682 ± 0.022) and forgets less (0.095 ± 0.033 vs 0.149 ± 0.009), but loses on task 2 (0.806 ± 0.049 vs 0.830 ± 0.047); the test-time delta is at most +0.003 within the noise of 0.004-0.010, and the page declares that the wrapper does not improve accuracy on this domain.
Fixed¶
RunningMoments.learn_onereturnednanon the first sample at1e307(inf * n_oldwithn_old = 0). The first sample now sets the state directly.- Attach on Windows without the resource tracker: the
spawnPool no longer hangs. Tests for the pickled handle, docstrings, docs page. - Review fixes 1-4 on
attention,continualandconsolidation(see the individual commits2299a13…54fb537).
Added¶
dense_armor/utility/data/shared.py: shared-memory containers (SharedArray,SharedList,SharedDict,to_shared) for process-to-process data sharing. They do not inheritRootand are not estimators; the module is independent and its tests do not callcheck_estimator. Pagedocs/api/data_shared.md, benchmarkbenchmarks/data_shared_memory.pyandbenchmarks/data_shared_memory.json.dense_armor/utility/neural/consolidation/: the consolidation code split into a package (vq.py,tree.py,memory.py,archival.py). No behaviour change; the tests oftest/neural/test_consolidation.pystill pass.
Changed¶
.github/workflows: the coverage flags are carried forward on docs-only commits (codecov.ymlupdated).ARCHITECTURE.md: lists the 12 missing modules (neural, stats, timeseries), dropsanwavandcollatz.
[1.2.1] - 2026-10-27¶
Fixed¶
OnlinePlattScaling: handle NaN in base predictions (fallback to 0.5).- Log-domain clipping (1e-15) in LogLoss, Beta, OnlineGaussianNB, Gaussian Mixture, GaussianNLL, Hoeffding Tree, and AMRules.
- Division and square root protections against zero variance in Gaussian PDFs, Correlation, Hoeffding leaves, SGT, OnlineGaussianNB, and Mixture.
- Exponential overflow protection (clamp to [-700, 700]) in Factorisation Machines, SGT, Mixture PDF, and Student-T PDF.
- Pre-existing mypy errors in hoeffding.py, mixture.py, and amrules.py.
[Unreleased]¶
1.2.0] - 2026-10-26
Changed¶
- README Speed section: one sentence on EFDT (26-58x faster than the adaptive random forest, at parity on sudden and gradual drift, 30.0 pp behind after the change on incremental drift, no native drift detection). The numbers come from
benchmarks/neural/efdt_vs_arf.py. GatedCrossAttention.forwardreturnsxq + g * attention(xq, xc)and notg * attention(xq, xc): a closed step returns the query unchanged. Deviation from the task specification, declared in the class docstring: a zero vector at a closed step breaks the gradient path of the query. The hand case and the closed-step test are updated.
Added¶
dense_armor/utility/neural/continual.py,generative_replay.py,consolidation/(vq, tree, memory, archival):EWC,GEM,AGEM,DeepGenerativeReplay,VQMemory,PQMemory,ResidualTree,LifecycleMemory,DualMemory,TheoreticalBound,ArchivalMemory.docs/neural/continual_learning.mdwith the eleven pieces.benchmarks/neural/continual_learning.py,memory_fidelity.py,public_benchmark.py,efdt_vs_arf.py(EFDT vs HAT vs ARF-10 on three drift types, with the declared -30.0 pp limit of EFDT on incremental drift). Three test files (continual 19, generative replay 5, consolidation 57).dense_armor/utility/neural/cross_attention.py,gated_attention.py,latent_attention.py,bottleneck.py:MultiHeadCrossAttention,GatedCrossAttention,LatentCrossAttention,BottleneckFusion.docs/neural/composable_attention.mdwith the four bricks.benchmarks/neural/composable_attention_figure.py. Four test files (A: 12, B: 16, C: 11, D: 14).
Fixed¶
HoeffdingTreeClassifierwithleaf="nb"(the default): a leaf in which any class has fewer than two samples now answers likeleaf="majority", inpredict_proba_oneand inpredict_one. Before, that class skipped every likelihood term and kept only its prior, while the other classes added one log-density per feature, so on an imbalanced stream the rare class got a probability close to 1 for ordinary samples. On a leaf with 300 samples of class 0 and one of class 1 (synthetic), the median probability of class 1 on 200 new class-0 samples was 0.953 and is now 0.0033. On the credit card fraud stream (284,807 rows, 492 frauds) the default configuration measured AUROC 0.513 and AUPRC 0.0032 before the change (dense-business,benchmarks/kaggle/n65_diag). On the sudden-drift stream oftest/ensemble/test_comparison.py(2,000 samples, 10 seeds) the single tree goes from a median accuracy of 0.9392 to 0.9965 andOnlineBoostingClassifierfrom 0.9758 to 0.9970, so the gain of boosting over the single tree (0.0365, Wilcoxon p = 0.0039) becomes 0.0005 (p = 0.125); the testtest_boosting_beats_single_treeis replaced bytest_boosting_not_worse_than_single_tree. Tests:test_nb_leaf_one_minority_sample_does_not_saturateandtest_nb_leaf_still_used_when_every_class_has_two_samplesintest/tree/test_hoeffding.py.
[1.1.35] - 2026-10-24¶
Changed¶
- The DOI of version 1.1.34 in the README badge, the README links and
CITATION.cff.
Added¶
docs/neural/attention.md, the page of the attention;docs/api/neural.md, the signature of every public class and function ofneuralandoptim.- The 16 sections of the pages
docs/neural/index.md,docs/neural/vision_layers.md,docs/neural/deep.md,docs/neural/sequence_layers.mdanddocs/neural/continuous.mdfor the neural classes that had no code example:Sigmoid,Tanh,Softmax,PRelu,SoftmaxCrossEntropy,BCEWithLogits,Huber,MaxPool1D,AvgPool1D,AvgPool2D,Upsample1D,Upsample2D,LastStep,MeanOverTime,AddPositions,OnlineNetClassifier. benchmarks/neural/check_examples.py, the script that checks that each class is in a code block of its page and that every block of the five pages prints the output written under it;test/neural/test_check_examples.py(4 tests).
Fixed¶
FFMClassifierallocates the factor array only for the features that have a field (before, a feature ofxthat was not infieldstook a row ofn_features * kfloats that was never read).- Sketches:
CountMinSketch,HyperLogLog,BloomFilterandSpaceSavinghaven_missing_and the read-only propertyn_missing;learn_oneon a sample without the watched key counts it instead of raisingKeyError, andtransform_onecounts it and returns{};mergeadds the counters. check_estimator:check_mutate_idempotentapplies the samemutatetwice and asserts that the state is the same as after the first (before it calledmutateonce and asserted nothing);check_learn_one_does_not_modify_xtakes the snapshot withcopy.deepcopy;check_memory_growth_boundedalso checksO(window)(it fills the window plus 8 samples, then asserts that 32 more do not grow the memory; estimators without an integerwindoware skipped); the message ofcheck_p99_within_budgetfor abudget_sunder 1e-3 s says "is below" the lower bound instead of "exceeds" it.- Stats:
RunningMoments.learn_manycounts every NaN or infinite value inn_missing_(before, 1 only for a block with no finite value) andRunningMomentsVector.learn_manycounts a channel absent from the dict;RunningMoments,RunningMomentsVector, the quantile estimators, the robust estimators andAutocorrelationcount a missing key or a value that is not a number inn_missing_instead of raisingKeyError;TDigest.quantile(0)andquantile(1)returnmin_andmax_, asDDSketchdoes;OnlineShrinkageCovariance.shrinkageuses the smallest number of complete pairs asn, so a sample with a NaN no longer inflates it; the sketch hash accepts aseedof2**32or more (taken modulo2**32). - Streams:
iter_csvraisesValueErrorforstartorstopwithouttime_col(before they were ignored);merge_by_timerefuses a NaN or infinite timestamp as it doesNone; the docstring ofiter_rosbagstates that an unknown topic gives an empty iteration. n_missing_with the read-only propertyn_missing(as the rest of the library) in the active-learning strategies, in all the bandit policies, in the factorisation machines, the linear models (glm,bayes,payload), the recommenders,skylineand the neural adapter, which had a plainn_missingattribute; reads ofn_missingare unchanged.VariableUncertaintyloses a condition that was always true; the docstring ofVariableRandomizedUncertaintystates that a largedeltacan give a negative threshold.- Anomaly:
StreamingDeviationScorer,HampelScorer,TukeyScorer,ChauvenetScorer,SigmaClipScorerandHampelFilterno longer raiseKeyErroron a sample without the watched key (or empty, or with a value that is not a finite number):learn_onecounts it inn_missing_,score_onereturns the pre-warm-up value,HampelFilter.transform_onereturns the input unchanged;StreamingDeviationDetector.updateignores a non-finite value instead of putting it in the window; the docstrings ofOIForest(score beforeready),OneClassSGD(meaning oft),MahalanobisDetector(identity fallback belowd + 1samples) andcurvature(not a differential curvature, name kept) state what the code does. - Bandits:
BayesUCBkeeps the variance of the Gaussian rewards with the Welford update (m2_, replacingsumsq_), stable for large rewards;offline_replayrefusesn_bootstrap < 1; the docstrings ofBayesUCBandThompsonSamplingstate that a reward>= 0.5is a success and that the Gaussian posterior ofThompsonSamplinghas a flat prior (notN(0, 1)). CategoricalNB:predict_proba_onecounts the categories of each feature once instead of once per class (O(c d) instead of O(c² d) category scans); the docstrings ofCategoricalNBandComplementNBstate how a missing key, aNoneor a non-finite value is handled.OnlineKMeans: during the warm-up a point of another dimension is counted inn_missing_(before it raisedn_seen_); the docstring states that the first centre is the point nearest to the mean (PatchCore starts at random) and thatcenters_has fewer thankrows with fewer thankdistinct warm-up points.FuncTransformer: withwhole=Truea function that does not return a dict raisesTypeError, and aSignalkeeps its units and missing mask when the returned names are the input names; the channel lookup is one dict instead ofnames.indexper key; the module docstring said "empty keys" where the code (and the argument docstring) meansNone.- Control:
kinematic_tracking_controllerandquintic_trajectoryraiseValueErrorinstead ofassert(the check stays underpython -O),quintic_trajectoryalso refuses arrays that are not 1-D, aTthat is not finite andn_samples < 1; the CBF functions refusen_substeps < 1; the docstrings state thatrate_limited_followerdoes not limit the first sample, thatdtis the sampling period, that the CBF filter returnsu_deson the obstacle and thatcbf_filtered_trajectorydoes not bound the speed. - Metrics:
mergeofPrecision,Recall,FBetaandF1refuses anotherpositiveoraverage;MeanAbsoluteError,MeanSquaredErrorandRootMeanSquaredErrorrefuse a change between a scalar and a vector (before, the scalar was added to joint 0);Rollingrefuses a metric that cannot undo an update (R2Score,RollingAUC) when it is built, and a missingtin thewindow_smode; the docstrings state thatRolling.mergeis for reporting, thatPointAdjustedF1.mergedoes not exist, thatRollingAUCignoresw, thatnab_scoreis not clamped at 0 and the labels ofLogLossandBrierScore. LogisticRegressionbinarises the label as its docstring says (a nonzero label is 1);LogisticRegression,LinearRegressionandBayesianLinearRegressionskip an infinite value and count it inn_missing;RecursiveLeastSquarescounts a missing key as 0.0 like the other linear models.BatchDecisionTreeClassifier.fitrefuses a row without a feature, a value that is not a number and a NaN or an infinite value; the unused_classes_is removed; the docstring states the schema, the answer on a missing feature and the budget.LEGO.percentile_targetsrefuses a set without a pair of the same class or without a pair of different classes;OnlinePlattScalingasks the base classifier once per call and not twice.OnlineModelSelection.current_bestraises before the models have run, as its docstring says;progressive_val_scoreandprogressive_val_proba_scorerefuse a negative delay and a delay in seconds on samples without a timestamp (before, nothing was scored until the end);evaluate_eventscallsscore_oneandlearn_oneof an anomaly detector withouttwhen they do not take it; the docstrings state what the metric receives fromprogressive_val_proba_scoreand what the trace holds.- Drift:
ADWINtakes its rangeRfrom the window that is left after an eviction (before, the minimum and the maximum of the evicted samples stayed and made the threshold grow);dense_armor.utility.driftexportsADWIN,CUSUMDriftDetector,KSWINandPageHinkley;BayesianOnlineChangepointsetsn_missing_in_resetlike the others;cusum_detectorwithreference="fixed"refuses a first window that holds a NaN or an infinite value (before, the whole stream was skipped without a message). Metro.enckeeps the sign of a negative value;diagscales each type of sample by its range and refuses one path with one array;anwavlabels the loudness as an estimate (RMS plus 3 dB); the docstrings ofABCollatz.calculate_jax_radand of the recommenders (Baseline,BiasedMF) state the limit of 255 and the order of the update ofmu.LSTM.backwardwithpeepholes=Trueadded the termp_o * do_preto the cell gradient twice, so the gradient carried to the previous step was wrong as soon asp_owas not zero (it starts at zero, so the gradient checks at initialisation could not see it); the second addition and a repeated assignment are removed, and two gradient checks run with non-zero peepholes.FFMClassifier.learn_onelearns the label0: withy = 0the gradient of eq. (5)-(6) was zero and the model never learned the negative class. The labels1and0, or+1and-1, now give the update of the logit loss; the update for+1and-1is unchanged.docs/neural/deep.mdheld its sections 7 to 11, the figure and the details twice; the repeated half is gone.ARCHITECTURE.mdlistsbocpd.py,vectorizer.py,shrinkage.py,similarity.py,batch.pyandfimt.py.- Controllers:
passivity_cbf_controller.solve_control_qpdrops the joint-limit box when the CBF and the box are jointly infeasible, as the 6-DoF controller does; both controllers raiseRuntimeErrorwhen even the CBF-only QP is not solved (before, OSQP's infeasibility certificate, of order 1e9, came back as the acceleration) and raiseValueErrorfor a robot with fewer joints than the task dimensions (3 or 6), where the manipulability gradient is NaN. DynamicAICodegen.run_pipeline_with_chunkingcarries the PRNG key across chunks and across calls: a dropout no longer repeats the same mask, and the result no longer depends onchunk_size.compile_pipelinenames the instruction whose parameter is not a number.BitwisePermutationEngine.apply_bitwise_swapaccepts a JAX array.- LLM bridge:
learn_jsonrefuses a missing ornulllabel (before,Nonewas learned as a class); the output ofembedmust be a 1-D sequence of numbers (before, a string of digits became one feature per character);predict_proba_jsonwrites the class keys aspredict_jsonwrites the class ("true", not"True");describenames a callable instance by its class. - Roles:
RealtimePipeline.learn_onepassessig.tto every step that takest;call_jsondispatchesupdate(x, t)to a drift detector and returnsdrift_detected;SafeEstimatorandUnitCheckedPipelinepassyandtonly to alearn_onethat declares them (before, an unsupervisedlearn_one(x, t)receivedyast);SafeEstimatormonitors a classifier with the 0/1 error. - Docstrings and notes:
filter_batch_scenariosrecalibrates the filter onself; the engine preserves the magnitude per element (it said "L2 norm");Signal.stackedgivest=Nonefor different timestamps; the MCP server drops a session idle for one hour at the nextdense_armor_stream_start, and its standalonerequirements.txtlists the core dependencies.
[1.1.34] - 2026-10-23¶
Added¶
dense_armor/utility/optim/:RMSProp;SGDwith momentum and Nesterov.dense_armor/utility/neural/: 10 new modules (clip, activations_more, schedules_more, convtranspose, bidirectional, norm_more, conv_more, positions, losses_more, online_vector);PRelu.params();clip_normin the four estimators.docs/neural/deep.mdanddocs/assets/neural/deep.png.
Added¶
dense_armor/utility/neural/:Autoencoder,VectorQuantizer,MatryoshkaLoss,Seq2Seq,OnlineAutoencoder;docs/neural/encoder_decoder.mdwith the guide and the figure.
Added¶
dense_armor/utility/neural/:OnlineNetRegressor,OnlineNetClassifier,HedgeNetClassifier,BackgroundLearner;docs/neural/continuous.mdwith the guide and the figure of continuous training.
Added¶
dense_armor/utility/neural/:ConstantLR,StepLR,CosineWarmRestarts,minibatches,Trainer;docs/neural/training.mdwith the guide and the figure of the training loop.
Changed¶
- The marker
heavy(tests above 14 seconds:test/dynamics, the payload and the experiment suites,ensemble/test_comparison.pyand a few more) moves 1060 seconds of the suite out of the matrix of the workflowtests, which now runs-m "not training and not heavy"; the workflowtests-slowrunstraining,heavy-dynamicsandheavy-restas three parallel jobs (Python 3.12, ubuntu) with the coverage uploaded to Codecov. - The five tests of
test/neuralthat train a network for minutes carry the markertraining; the workflowtestsruns-m "not training"and the new workflowtests-slowruns them on one job (Python 3.12) whendense_armor/utility/neural/,test/neural/orpytest.inichange. A test forMaxPool2D.backwardbeforeforward, the format of three files, and a positive wording indocs/neural/vision_layers.md.
Added¶
dense_armor/utility/neural/:Conv1D,Conv2D,MaxPool1D,MaxPool2D,AvgPool1D,AvgPool2D,Flatten,RNN,GRU,LSTM,MultiHeadSelfAttention,TransformerBlock,LastStep,MeanOverTime,AddPositions,cnn,recurrent_net,transformer_encoder;docs/neural/vision_layers.mdanddocs/neural/sequence_layers.md.dense_armor/ml/: the machine learning classes, which read the whole table infit: statistics, preprocessing, linear models, naive Bayes, nearest neighbours, multiclass, multi-output, distributions, trees (CART and Mondrian forests), anomaly detection (Hampel, Tukey, Chauvenet, sigma clipping, Local Outlier Factor), change detection (CUSUM with a fixed reference) and vision (Horn-Schunck flow, motion mask, phase correlation, random projection, PCA); each module lists inSTOPPEDthe classes whose reference defines no algorithm for the whole dataset;docs/ml/anddocs/compare/hold the pages and the comparison with the online classes.
Added¶
dense_armor/utility/neural/:Sequential,Parallel,mlp,resnet_mlp;docs/neural/chains.mdwith the guide and the figure of the MLP on a synthetic two-moons set.
Added¶
dense_armor/utility/neural/:Dense,Embedding,Dropout,Residual,BatchNorm,LayerNorm,AsTransformer;docs/neural/layers.mdwith the guide and the figure of the two normalisations.
Added¶
dense_armor/utility/neural/:Param,he_normal,xavier_normal,glorot_uniform, five activations and four losses (mse, softmax cross-entropy, bce-with-logits, Huber) withforward/backward, andgradcheckagainst central finite differences;docs/neural/index.mdwith the guide and the figure of the five activations.
Changed¶
- The DOI of version 1.1.33 in the README badge, the README links and
CITATION.cff.
[1.1.33] - 2026-10-21¶
Added¶
notebooks/drone_actuator_fault.ipynb: the actuator-fault pipeline on synthetic flights, with the CUSUM thresholdhagainst false alarms per hour.Documentation,ChangelogandIssuesin[project.urls].docs/drones/index.mdlists every page of the drone section.edge/power.py,edge/energy_bench.py,test/drones/test_power.py,test/drones/test_energy_bench.py,docs/drones/jetson_energy.md: the energy of a run (trapezoid integral of a power trace, net of the idle), a paired batch-vs-online energy benchmark, the four calibration lines of Table I of 2306.13107, and one command for a Jetson board. Every energy value is synthetic or null: no reduction on a board is claimed.edge/board_selftest.sh: the energy section after the existing steps, onPOWER_NODE.
Changed¶
- The licence is the GNU Affero General Public License v3.0 or later (
LICENSE); the Business Source License 1.1 text of the releases up to 1.1.32 is inLICENSE-BUSL-1.1-until-1.1.32.md.pyproject.toml,CITATION.cff, the README anddocs/license.mdfollow. - The README says that the library also learns from a whole dataset (
fitand the batch versions) in the three places that said the opposite.
Changed¶
- The DOI of version 1.1.32 in the README badge, the README links and
CITATION.cff.
[1.1.32] - 2026-10-21¶
Added¶
TextVectorizerindense_armor/utility/preprocessing/vectorizer.py,stop_wordsinTokenizer,norminTFIDF,cosine_similarity,cosine_similarity_many,SparseCosineIndex(query_many,query_many_sparse,top_k_threshold) indense_armor/utility/stats/similarity.py,TextVectorizer.transform_matrix,benchmarks/text_retrieval.py,benchmarks/primitives.pyanddocs/preprocessing/text_vectorizer.md.dense_armor/utility/drift/bocpd.py(BayesianOnlineChangepoint, Adams and MacKay 2007) anddense_armor/utility/stats/shrinkage.py(OnlineShrinkageCovariance, Bodnar, Gupta and Parolya 2014). Seven finance pages and six finance modules:regime.py(BOCPD on daily returns, 6 alarms in 2.48 years of calm data on GSPC, synthetic delay 5 samples),drift_vs_noise.py(raw against pre-averaged, p=0.0082 A12=0.675 calm, p=0.0001 A12=1.000 delay),portfolio.py(minimum-variance and inverse-volatility from the shrunk covariance),order_book.py(order flow imbalance, equation 4 of Cont et al. 2010),stress.py(synthetic flash crash: surveillance 0/5 alarms, execution 2.0 refusals median),connectors.py(BoundedStreamwith three policies,finance-streamextra).benchmarks/finance/for the five of them anddocs/finance/{regime,drift_vs_noise,portfolio,order_book,stress,streaming,timing}.md.pipelines/drones/, a drone section of nine pipelines over the library primitives, on synthetic streams:actuator_fault(CUSUMDriftDetector,OnlineRobustMahalanobis; one episode 0.02 s after the fault, 0 false alarms),sensor_consistency(CUSUMDriftDetector; 3 episodes at 0.28 / 1.47 / 2.67 s),propeller_health(SDFT; band [40, 55] Hz 0.74 to 57.10, peak 46.88 Hz),battery_endurance(AdaptiveConformalRegressor,LinearRegression,AdaGrad,TDigest; q50 22.5 min, coverage 0.923),safety_filter(cbf_safety_filter_live; 2.0 m/s to 0.285 m/s),link_health(twoCUSUMDriftDetector; 0.19 s after the dropout),visual_landing(GlobalMotion,DenseFlow,MotionMask,FrameAnomaly; mean shift error 0.731 px, FrameAnomaly 0.3100 to 0.6433, MotionMask 0 to 185 px),static_vs_online(a CART tree against a Hoeffding tree; 1.0000 against 0.9920, paired Wilcoxon p = 0.0020, A12 1.000).pipelines/drones/alfa.py: reader for the ALFA dataset (Keipour, Mousaei & Scherer 2020, DOI 10.1184/R1/12707963), the same fields asread_mavlink_logplus the fault ground truth, andalfa_fault_time.benchmarks/drones/alfa_actuator.pyandkaggle/drones_alfa_actuator.pywith aSMOKEswitch: the actuator-fault pipeline on the processed ALFA flights, delay and false alarms per hour over the flights.dense_armor/utility/tree/batch.py:BatchDecisionTreeClassifier, a CART tree fitted on the whole training set (Breiman et al. 1984, chapter 2; Gini index as in Bourel et al. 2026, page 8).OnlineBaggingClassifier.fit_batch, the batch protocol of Oza and Russell 2001.edge/:edge_bench.py,mqtt_bridge.py,install.sh,Dockerfile.arm64,Dockerfile.jetson, the systemd unit andboard_selftest.sh.docs/drones/: nine pipeline pages,start_here.md,ardupilot.md(ArduPilot against PX4, field by field, in thepymavlinkdialects) andjetson_power.md(thenvpmodelmodes of each Orin module, the commands, and a measurements table to be filled with the numbers of a board)..github/workflows/arm64-smoke.yml: the arm64 image builds under QEMU emulation andedge.edge_bench --smokestarts on it, onedge/**andpipelines/drones/**changes.
Changed¶
pyproject.tomlhas a[tool.ruff]section (target py310,.claudeexcluded, rules E4, E7, E9, F, I, UP) andruff check --fixis applied to 44 files ofdense_armor: import order, annotations, unusednoqa. The 30 errors left indense_armorhave no safe fix.docs/fit.mdlists the algorithms that work on the whole dataset:BatchDecisionTreeClassifier,OnlineBaggingClassifier.fit_batchandTextVectorizer.fit.
[1.1.31] - 2026-10-17¶
Added¶
CUSUMDriftDetector(reset_on_alarm=False)and the countern_alarms_: withreset_on_alarm=Truethe statistic and the reference restart after an alarm, so a sustained step gives one alarm and not one per sample. The default is the old behaviour.benchmarks/casper_hours_episodes.pyandkaggle/casper_hours_episodes.pycount alarm episodes per hour next to the count of flagged samples.learn_manyonRunningMomentsandRunningMomentsVector: a vectorised batch update, equal to the row loop within1e-9(tested).dense_armor/utility/_validation.py:as_dict,finite_rowandis_number, the validation that several estimators copy; the existing classes are not changed.FIMTDDRegressor.n_missing, the name the rest of the library uses;n_missing_stays.benchmarks/flow_levels.pyand its JSON: the error of the optical flow against the true shift for one to four pyramid levels and for the intensity scales, with and withoutnormalize_intensity;docs/vision/flow.mdstates what each number of levels recovers.benchmarks/seed_audit.pyand its JSON: the estimators whose output depends on chance, and the default of theirseed;benchmarks/hotspots.py: the profile of five models.
Fixed¶
- Type errors of
mypyinvision/window.py,drift/cusum.pyandvision/dense.py, with no change of behaviour.
Changed¶
- The DOI of version 1.1.30 in the README badge, the README links and
CITATION.cff.
[1.1.30] - 2026-10-17¶
Master program 1 closed¶
The first master program, the online-learning toolbox in 21 sections, is complete. What each section delivers:
- Base and checks, statistics, preprocessing, metrics and evaluation, pipelines and datasets: the
rolesbase classes andcheck_estimator, the robot layer (timestampedSignal, real-time contract, uncertainty, safe estimators, units), moments, robust statistics, quantile sketches, scalers and feature selection, prequential evaluation, streams and the synthetic datasets. - Models: linear models and optimisers, naive Bayes, neighbours, multiclass and multi-output wrappers, probability distributions, Hoeffding trees, FIMT-DD regression model trees, adaptive random forests, Mondrian forests, extra trees and AMRules, with the numeric go/no-go for the aggregated Mondrian forest and the online extra trees.
- Ensembles, bandits and active learning, factorization machines: online bagging and boosting, exponentially weighted average, voting and stacking; seven bandit policies with offline replay and four labelling strategies; factorization machines (plain, field-aware, field-weighted, higher-order), three recommenders, the sliding DFT and the skyline.
- Anomalies, drift, clustering, time series, uncertainty: robust detectors, Half-Space Trees, LODA, one-class SGD, online isolation forest, the local outlier factor; CUSUM, Page-Hinkley, ADWIN2 and KSWIN; online k-means and DenStream; online AR, Kalman filters and the cycle residual; adaptive conformal intervals.
- Protection, control and the LLM bridge: arbiter, healing and the real-time guard; kinematic controller, trajectories and passivity; embeddings as features and estimators as JSON tools.
- Native vision: frame streams and video files, features, motion, tracking, registration, dense flow, patch memory, with eight guide pages and twenty figures.
- Real robot (CASPER, UR3e): the cycle-residual CUSUM detects the labelled anomaly 15.00 s after it begins. The same configuration on the 15 normal hours before it fires a median of 5,696 false alarms per hour, with 2 hours at zero: the page states both. The dataset has no lidar, so the page carries a lidar benchmark that is a simulation and says so. The IMU file has no timestamp column and cannot be tied to the arm by cross-correlation (strongest correlation 0.087), so no IMU delay is reported.
Changed¶
- The video documentation is split into eight guide pages under
docs/vision/, each with a figure fromdocs/assets/vision/make_figures.py.
Added¶
- Bandit policies:
EpsilonGreedy,UCB1,UCB1Tuned(Auer, Cesa-Bianchi and Fischer 2002),ThompsonSamplingandBayesUCB(Chapelle and Li 2011, Kaufmann, Cappe and Garivier 2012),Exp3(Auer et al. 2002) andLinUCBDisjoint(Li et al. 2010). Offline evaluation on a logged stream:offline_replay(Li et al. 2011). Robot test:RobotKpSelector, UCB over the gainkpofkinematic_tracking_controlleronSyntheticArm. Guide:docs/bandit/index.md. Benchmark:benchmarks/bandit_quality.py, 20 seeds, one sudden change, regret against the oracle and against the best fixed arm. - Active learning:
RandomStrategy,FixedUncertainty,VariableUncertainty,VariableRandomizedUncertainty(Zliobaite et al. 2011). Guide:docs/active/index.md. fit(X, y=None, t=None, epochs=1, shuffle=False, seed=None)on the base classRoot: one offline entry point for every online estimator. It feeds the whole dataset tolearn_one, row by row, forepochspasses, optionally shuffled. Nothing existing changes. Guide:docs/fit.md.- Ensembles of online learners:
OnlineBaggingClassifierandOnlineBoostingClassifier(Oza and Russell 2001),ExponentiallyWeightedAverageRegressor(Freund and Schapire 1997, Hedge rule and its equation 9 bound), and theVotingClassifierandStackingClassifierwrappers. Guide:docs/ensemble/index.md. Benchmark:benchmarks/ensemble_quality.py, 20 seeds, one sudden change, paired Wilcoxon and A12 effect size. - New package
dense_armor/utility/facto/withFMRegressorandFMClassifier(Rendle 2010),FFMClassifierandFFMRegressor(Juan et al. 2016),FwFMClassifierandFwFMRegressor(Pan et al. 2018),HOFMClassifierandHOFMRegressor(Blondel et al. 2016). - New package
dense_armor/utility/reco/withBaseline,BiasedMFandFunkMF(Paterek 2007, Koren et al. 2009),learn_one(user, item, rating),predict_one(user, item)andrank. - New module
dense_armor/utility/misc/sdft.pywithSDFT(the N-point DFT of the last N samples in O(N) per sample, Jacobsen and Lyons 2003). - New module
dense_armor/utility/misc/skyline.pywithSkyline(non-dominated points of a sliding window, Börzsönyi et al. 2001, Tao and Papadias 2006). - New script
benchmarks/facto_quality.pyanddocs/assets/facto/figures.jsonwith every number quoted on the two guide pages. - New pages
docs/facto/index.mdanddocs/reco/index.md. FIMTDDRegressor(utility/tree/fimt.py): FIMT-DD regression model tree (Ikonomovska 2012) with an extended binary search tree per leaf, perceptron leaves and an alternate tree grown after a Page-Hinkley alarm.DenseFlow(normalize_intensity=False): an optional normalisation, off by default, that makes the flow independent of the intensity scale;FrameWindowresets when the frame shape changes and counts the changes inshape_changes_;FrameAnomalyis tested on 1-, 3- and 4-channel frames.benchmarks/video_quality.py --size WxH --smokeandkaggle/video_quality_kaggle.pyfor the long measurements at 1280x720 and 1920x1080.benchmarks/lidar_sim.pyandbenchmarks/lidar_sim.json: a synthetic lidar benchmark (simulation) with two detectors and four faults;benchmarks/casper_imu_alignment.pyand its JSON: the IMU alignment attempt.benchmarks/casper_hours.pyandbenchmarks/casper_hours.json: false alarms of the headline CASPER configuration on every normal hour.ARCHITECTURE.mdanddocs/api/general.mdlist the bandit, active, factorization, recommender and small-structure packages.
Fixed¶
OnlinePlattScaling(learn/calibration.py): the annotationbase.Classifiernamed a module that does not exist; it isClassifier.from __future__ import annotationsis removed from the seven files that had it, since Python 3.10 supports the syntax directly.
Changed¶
- The claim register and the table of numerical claims left the published docs: they are now
benchmarks/claims_register.mdandbenchmarks/numbers_register.md. The navigation lists Speed once.
[1.1.29] - 2026-10-16¶
Changed¶
- Speed (#184): ADWIN rewritten as ADWIN2 (exponential buckets, Hoeffding cut,
Bifet and Gavalda 2006), 13x to 38x faster per update depending on the
machine;
AdaptiveRandomForestClassifier.predict_oneno longer builds the probability dict (5x to 7x faster); the Hoeffding tree checks for a split everygrace_periodsamples (1.6x to 1.8x faster). Before and after on three machines inbenchmarks/speed_log.md, harness inbenchmarks/speed.py. - ADWIN can flag a change on several consecutive updates; the docs say so and
the robot-stream table of
docs/drift/detectors.mdis measured again with this version (17.7 s delay, 0 missed, 1.4 false alarms per hour). check_estimatorcomputes the ROC AUC with its own Mann-Whitney U, with no optional dependency.
Fixed¶
- The provenance signature of
dense_armor.corefollows the package version (it fell back to 1.0.11) (#187). SafeEstimator.restorereads the checkpoint envelope with an unpickler that refuses every global and verifies the digest before the model state is unpickled;saveandrestoretake an optionalkeyfor an HMAC-SHA256. The state is still a pickle: restore only trusted checkpoints (#187).- MCP streaming sessions are capped at 256 and dropped after an hour idle;
importing
dense_armor.dynamicsno longer loads the QP solver;nvidia-smiis called without a shell;one_sided_arlreturnsinfon exponent overflow instead of a warning (#187).
Added¶
- Quality benchmark of the building blocks (20 seeds, 3 streams) and the claim
register
docs/quality.md(#181, #183);check_estimatorreports "not applicable" checks instead of skipping them silently. - Tests for the lines of the speed patch (#186).
[1.1.28] - 2026-10-15¶
Added¶
- Naive Bayes (
dense_armor.utility.bayes):MultinomialNB,BernoulliNB,ComplementNB,CategoricalNB; distributions (proba):Beta,Gaussian,Multinomialwith a pureinit_state/stepform; nearest neighbours (neighbors):KNNClassifier,KNNRegressor,LazySearch; multiclass wrappers (multiclass):OneVsRestClassifier,OneVsOneClassifier,OutputCodeClassifier; multi-output estimators (multioutput): per-output models and classifier, regressor and probabilistic chains. Five docs pages with figures (#167). - Forests (
dense_armor.utility.forest):AdaptiveRandomForestClassifier,AggregatedMondrianForestClassifierandAggregatedMondrianForestRegressor(#168), with a bound on the size of every tree (#170);OnlineExtraTreesRegressor(#173). Docs pagedocs/forest/. - Rules (
dense_armor.utility.rules):AMRulesRegressor(#172). Docs pagedocs/rules/. - Anomaly detection:
LocalOutlierFactor(#166) and the Streaming Half-Space Trees of Tan et al. 2011 (#157). - Finance: nine pipelines (markets, fraud, intrusion, credit scoring), one page each (#161), with the numbers remeasured on closing prices and figures (#162).
- README: a five-feature online classifier quickstart, "Who is this for", "What it expects", "What changes" and a use-cases section (#147, #148, #149).
- One plain-words sentence at the top of every docs page (#160).
Changed¶
check_estimatorruns with no declared skip on the classes ofbayes,neighbors,multiclass,multioutput(#171) andforest(#169).- API reference grouped by the real module (#152); docs of the anomaly pages aligned with the code (#154).
Fixed¶
- LODA: the projection is normalised with its theoretical minimum and maximum
(#153).
OnlineRobustMahalanobis: the step index counts from the first sample (#156). - Time series: code matched to the three papers, with a rolling cycle template (#163). Optimizers, linear models and payload estimator: fixes from the review (#165).
LocalOutlierFactorgives finite scores on duplicates and a documented value on malformed input (#166).
Every commit since 1.1.27 is listed in the release notes on GitHub.
[1.1.27] - 2026-10-13¶
Added¶
- Time series package (
dense_armor.utility.timeseries):OnlineAR,OnlineVAR(TIRSO, Zaman 2019 eq. 25 with the1{n != n'}indicator on the group-lasso penalty),KalmanFilter,VBAdaptiveKalman(Mbalawata 2013 Algorithm 1),InnovationAdaptiveKalman(Popescu 2026),CycleResidual(period detection by autocorrelation, median template, bounded buffer). Four new docs pages underdocs/timeseries/and three figures. - Linear models (
dense_armor.utility.linear):LinearRegression,LogisticRegression,BayesianLinearRegression(predictive interval),PayloadEstimator(tracking figure). Docs underdocs/linear/. - Online optimizers (
dense_armor.utility.optim):SGD,AdaGrad,Adam,FTRLProximal(McMahan 2011),OnlineNewtonStep. Each with a learning-rate choice figure and a docstring regret bound. Index page underdocs/optim/. - Three new comparison pages:
docs/vs_kalman.md,docs/robustness.md(adversarial attacks on the shields, weakest point stated),docs/limits.md.
Changed¶
core/(14 files): docstrings in English, inline#comments removed, missing type hints added (chunk.py,profiler.py,vector.py). No behavioural change.- README restructured: short answers at the top ("Who is this for", "Use cases", "What it expects from you", "What changes when you use it") before the long part. Quickstart is now a 5-feature online classifier on a drift stream with the measured accuracy, followed by a rate-limiter example on a real Panda URDF.
pyproject.toml:ruffadded to the[docs]extra somkdocstringsformats signatures without the "install Black or Ruff" warning.
Fixed¶
griffewarnings inmkdocs build --strictforcodegen_engine,codegen_instance,stabilizer_instance,base_state: missing parameter annotations. Strict build now passes.dense_armor/__init__.py:__version__was at1.1.25whilepyproject.tomlwas at1.1.26. Both now at1.1.27.
[1.1.25] - 2026-10-09¶
-
Streams, datasets and pipelines:
dense_armor.utility.stream(iter_array,iter_csvwith constant memory and list-valued columns,iter_rosbagfor ROS 1 and ROS 2 bags with the new extra[ros],merge_by_time,shuffle);dense_armor.utility.datasets(Casperreader for a local copy of the CASPER UR3e dataset,DriftStreamwith sudden, gradual and incremental drift after Ksieniewicz and Zyblewski 2020,SyntheticArmlabelled joint stream from a URDF with friction or payload faults);dense_armor.utility.compose(Select,Discard,FuncTransformer).Protectedpassestonly to models that accept it;HampelScorer.thresholdexposesn_sigmasforAnomalyGate. -
Incremental trees and online forests (
dense_armor.utility.tree):HoeffdingTreeClassifier(Hoeffding bound,explain_one),HoeffdingAnytimeTreeClassifier(statistics on every node, splits revisited and replaced or collapsed; Manapragada 2018),HoeffdingAdaptiveTreeClassifier(a drift detector on each node, alternate subtrees),SGTRegressor/SGTClassifier(stochastic gradient trees with the t-test of Gouk 2019, eq. 16 and 18),MondrianForestRegressor/MondrianForestClassifier(online Mondrian trees, Algorithms 2 and 4 of Lakshminarayanan 2014, predictive mixture of eq. 3 of the 2016 paper, variance growing away from the data). OnlinePlattScaling: while the base classifier has no opinion yet (emptypredict_proba_one),learn_onetrains the base only and leaves the calibration untouched; a base that knows only the negative class givesp = 1 - p(False)instead of 0.5. Phishing log-loss of the docstring example: 0.3499.
[1.1.24] - 2026-10-08¶
- Patch-level anomaly detection in the vision package:
PatchFeatures(oriented-gradient histogram, mean and contrast per patch, averaged over a 3×3 neighbourhood) andPatchMemory(bank of normal patches, image score = largest nearest-neighbour distance, PatchCore eq. 6), online with a bounded reservoir or reduced once with the greedy coreset of eq. 5 (Roth 2021);benchmarks/vision_rad_patches.py: mean image-level AUROC 0.748 on RAD at 320×240 (baseline 0.497). -
Robot checks call a
learn_one(x, t)with the sample alone, so the timestamp is never set to the label placeholder. -
Online clustering (
dense_armor.utility.cluster):OnlineKMeans(sequential, optional half-life for drifting streams),DenStream(micro-clusters with a damped window, outliers kept apart, macro-clusters on demand);BagOfVisualWordsin the vision package (k-means vocabulary over patches, word histogram per frame). -
Preprocessing (
dense_armor.utility.preprocessing): online scalers per feature and per joint (standard, exponentially weighted, robust, min-max); joint velocity, acceleration and jerk on real timestamps without lag, joint power; text tokenizer, bag of words, TF-IDF (Silajev 2026), signed feature hashing (Weinberger 2009); variance threshold and k-best selection; queue-based resampling for imbalanced streams (Malialis 2018). -
Native online vision (
dense_armor.utility.vision):CameraStream(OpenCV, extra[vision]),ImageFolderStream,ArrayStream;FrameFeatures(oriented-gradient histograms, intensity moments, Lucas–Kanade flow);RandomProjectionandIncrementalPCA. benchmarks/vision_rad.py: native vision on real robot images (RAD); mean image-level AUROC 0.497 (chance level), reported as a baseline.FrameFeatures(flow_levels=n): coarse-to-fine Lucas–Kanade on a 2×2-average pyramid with bilinear warping (Ziani 2025, section 3.2, eq. 9);gray=Falseadds per-channel colour moments.- Pipelines built with
|now train every intermediate step (transform, then learn) and passtonly to steps that accept it.
[1.1.23] - 2026-10-08¶
- Online metrics (
dense_armor.utility.metrics) and evaluation (dense_armor.utility.evaluate): classification, regression, intervals, rolling windows and ROC-AUC, prequential with delayed labels, model selection, event metrics (range-based precision/recall, NAB, detection delay, false alarms per hour). AdaptiveConformalRegressorpassestonly to models that accept it.- Drift detectors
PageHinkley,ADWIN(Hoeffding bound with online range) andKSWIN;CUSUMDriftDetector(reference="fixed")no longer stays silent after a NaN or flat start;benchmarks/drift_comparison.pycompares the four on the CASPER stream.
Changed¶
utility/orca.Orca.protect_and_forward: vectorized the entry-shield stage -- the previousfor b in range(B)Python loop called_execute_4_phase_input_shieldonce per row (once per element of the batch), each a separate JAX dispatch. Same dispatch-dominated pattern already found and fixed incore/hybrid_engine.hybrid_shieldfor Armatura. New_execute_4_phase_input_shield_batchprocesses all B rows in one call via a newAdaptiveSignalStabilizer.filter_batch_scenarios_independent(core/engine.py): each row gets its OWN calibration (calibrate_macro_context_independent), not one shared across the whole batch like the existingfilter_batch_scenarios-- rows with different scale/noise no longer contaminate each other's threshold, matching the old loop's per-row-independent behavior exactly. Verified bit-for-bit (float64) against the loop version, including a test that specifically checks one row's huge injected spike does not change another row's output (test/test_orca_vectorized_batch.py). Real measured latency, synthetic B=500/F=10 batch (same shape as the APT29 + AI-shield experiment): median of 10 calls after JIT warmup, 750.6ms -> 2.3ms (~321x).chunk_thresholdbehavior unchanged for the one case this doesn't cover (a single row's own flattened size exceedingchunk_threshold, which falls back to the original per-row/per-chunk loop, untouched). No public API change.
Changed¶
core/hybrid_engine.hybrid_shield: vectorized withjax.vmap-- the previousfor i in range(2, n)Python loop calledcalculate_phi_ab/calculate_vettore_dinamico/evaluate_phi_triggeronce per point. Profiling on a real 500-point security-telemetry series (Sysmon EventID10, APT29) showed 87% of the time in JAX dispatch itself (~2000jnp.arrayconstructions, 3.2Misinstancecalls), not in the actual per-point math -- pure Python-orchestration overhead, not a real compute limit. Verified bit-for-bit against the original loop on that same real series and on synthetic edge cases (isolated spike, sustained step, with/without an external reference, n=0..3): identical output and trigger in every case. Real measured latency on that 500-point series (median of 20 calls after JIT warmup): 292.4ms -> 29.8ms (~9.8x). No public API change;baseline/scale/ipgstill read onlyprocessed(the sanitized raw series), neveroutfrom a previous step, per the no-self-contamination invariant this function has always documented.
[1.1.19] - 2026-09-05¶
Added¶
dynamics/passivity_cbf_controller.py(solve_control_qp): task-space passivity + singularity-avoidance CBF controller (Kurtz, Wensing & Lin 2021, arXiv:2109.13349) for any robotRigidBodyModelcan load. A small QP over joint acceleration, constrained byVdot<=0(passivity) and an exponential CBF keeping the manipulability index above a declared floor. Two-step promotion from Dense-Evolution-Discovery, same asRigidBodyModel: Experiment 61 built the same controller hardcoded to one Kinova Gen3; Experiment 63 replaced the hardcoded calls withRigidBodyModel's API and re-validated on the same three robots (Kinova Gen3 7-DoF, Kinova Gen3 6-DoF, Franka Emika Panda), each driven toward its own true singularity, manipulability held within 0.1-1.8% of the declared floor in every case. Found and fixed a real OSQP-infeasibility bug along the way (soft passivity constraint dropped in favor of the hard CBF one when the solver reports infeasible). New dependency:osqp.- Real per-joint position/velocity limits:
RigidBodyModelnow parses each joint's real<limit>tag (+/-infwhere the URDF declares none);solve_control_qpenforces them as an additional CBF (Kurtz et al.'s own "joint" constraint type), added only when a robot's URDF has a real finite limit somewhere. Real number, Franka Pandajoint4(range[-3.1416, 0.0]) at its bound with velocity driving past it: unconstrained commandqdd=-205.8clamped to the real box[-7.175, -4.999]. dynamics/six_dof_pbc_cbf_controller.py(solve_control_qp): full 6-DoF (position + orientation) generalization ofpassivity_cbf_controller.py, tracking a link's full pose viaRigidBodyModel's newlink_pose/link_spatial_jacobian. Attitude error uses Lee, Leok & McClamroch (2010)'s SO(3) formula instead of an RPY-based one, avoiding gimbal lock. Promoted from Dense-Evolution-Discovery Experiment 65: validated with an exact gravity-compensation check at zero position and orientation error (machine precision), and a real closed-loop run converging a 10cm/30-degree offset to near-zero (position 1e-6 m, orientation 1e-4) over 1000 control ticks; also validated on the same 3-robot bar aspassivity_cbf_controller.py, which surfaced a real second-level OSQP infeasibility (the 6-DoF manipulability measure can be well below the 3-DoF one at the same configuration, making CBF+box jointly infeasible) -- fixed with a third fallback level that drops the box and keeps only the CBF..xacrofile support:RigidBodyModelnow accepts.xacromacro files directly, expanding them via the realxacropackage before parsing. Promoted from Dense-Evolution- Discovery Experiment 66, which found and fixed a real inconsistency in the Franka Panda's own published macros (clvrai/furniture): a hand-attachment link commented out in the arm macro but required by the hand macro. New dependency:xacro.<mimic>joint support: a joint with a<mimic joint="..." multiplier="..." offset="..."/>tag no longer gets its own independent coordinate -- its motion is computed from its master's, chain-ruled onto the master's own column in the hand-built geometric Jacobian. Promoted from Dense-Evolution-Discovery Experiment 67, checked against a real central finite difference oflink_pose(Franka Panda gripper fingers) to under 1e-5.
[1.1.18] - 2026-09-05¶
Added¶
dynamics/urdf_dynamics.py(RigidBodyModel): Euler-Lagrange rigid-body dynamics -- M(q), C(q,qdot)qdot, g(q) -- parsed from any URDF file (stdlib xml.etree, no new dependency), not one hardcoded robot. Newdynamics/subpackage, separate fromutility/, since it needs an actual physical model file and returns torque-level quantities. Two-step promotion from Dense-Evolution-Discovery: Experiment 61 built the same dynamics (JAX autodiff, not hand-derived Christoffel symbols) but hand-transcribed from one Kinova Gen3's URDF -- the reason it wasn't promoted then. Experiment 62 replaced the hardcoded tables with a parser and re-validated on three independent robots (Kinova Gen3 7-DoF, Kinova Gen3 6-DoF, Franka Emika Panda -- different manufacturer, prismatic joints): cross-checked to machine precision against Experiment 61's own numbers, then mass-matrix SPD and energy-conservation (correct 4th-order RK4 convergence) verified fresh on all three.
[1.1.17] - 2026-09-04¶
Added¶
utility/cbf_filter.py(cbf_safety_filter_live): single-control-tick counterpart tocbf_filtered_trajectory-- for a real-time loop reacting to one sensor callback at a time (a realdtper tick) instead of filtering a whole pre-recorded array offline. Promoted after a real live ROS2/Ignition safety loop (Dense-Evolution-Discovery Experiment 58) needed exactly this and had to reconstruct it by hand fromcbf_filtered_trajectory. Reuses the same substep math already validated on SO-101/ALOHA -- not a new validation domain, an ergonomic single-tick wrapper around it. Verified directly: converges exactly to the declared safety boundary and never crosses it (max reached 2.199999999999889 for a 2.2 boundary).utility/trajectory.py(quintic_trajectory): closed-form, minimum-jerk-continuous point-to-point trajectory generator for any number of joints -- fills the gaprate_limiter/cbf_filterdon't cover (neither generates a reference to track). Deliberately scoped down from two real papers proposing much larger URDF/dynamics-aware optimizers (Lozer et al., Robotics and Autonomous Systems; Fried & Paternain, arXiv:2412.07859 -- both read in full before writing any code). Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101, ALOHA, 20 real joint excursions): quintic peak velocity always lower than the real recorded peak velocity for the same start/end/duration -- expected, since it is the smoothest possible path, not a bug.utility/kinematic_controller.py(kinematic_tracking_controller): closed-form feedforward-plus-proportional kinematic tracking controller -- turns a reference fromtrajectory.pyinto a velocity command forrate_limiter/cbf_filter. Searched for a real "universal controller" paper first (Wu & Tan 2025 -- best fit, paywalled; Scruggs -- real, needs infinite-dimensional convex Youla optimization; Califano et al. -- real, needs Hamiltonian mechanics; classical PD+gravity-compensation -- needs a real dynamics model, contradicting this stack's single-integrator scope). What ships instead:u = qd_ref + kp*(q_ref - q), with an exact closed-form exponential convergence guarantee, verified numerically. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101, ALOHA, 20 real joint excursions), chained withquintic_trajectory: every real excursion recovers from a real disclosed nonzero initial tracking error and converges.
[1.1.16] - 2026-09-04¶
Fixed¶
dense_armor/__init__.py:__version__was still hardcoded to"1.1.14"after the 1.1.15 release -- it drivesdense_armor_health'sdense_armor_versionfield in the MCP server, so the running package misreported its own version. Real regression found by checkingdense_armor.__version__right after installing 1.1.15 from PyPI, not caught before that release.pyproject.toml'sversionfield was already correct; only the Python-level constant was stale.
[1.1.15] - 2026-09-04¶
Added¶
utility/cbf_filter.py(cbf_safety_filter,cbf_filtered_trajectory): geometric Control Barrier Function safety filter -- never lets an applied command enter a forbidden region, complementingrate_limiter.py's rate-of-change bound. Grounded in Ames et al. (2019), "Control Barrier Functions: Theory and Applications" (2019 ECC, arXiv:1903.11199) -- the same theory SAFER-Splat uses, applied to a known geometric obstacle instead of SAFER-Splat's GPU-bound Gaussian-Splatting perception. Real numerical finding disclosed, not hidden: the CBF's continuous-time guarantee needs discrete sub-stepping (20/sample default) to hold on real commands that can jump substantially between samples. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101 6-DoF 30Hz, ALOHA bimanual 14-DoF 50Hz): 100% invariance from safe starting conditions on both (17/17, 38/38), minimal invasiveness 99.9%+ exact on SO-101 and perfectly exact (0/18444) on ALOHA.utility/rate_limiter.py(rate_limited_follower): causal velocity+acceleration command limiter -- bounds how fast an applied command can physically change instead of classifying whether a deviation is real, after a causal neighbor-consensus classifier approach hit a structural dead end (1.7% win rate vs a trivial moving median, 120 real trials). Grounded in Berscheid & Kroger (2021), "Jerk-limited Real-time Trajectory Generation" (RSS 2021, arXiv:2105.04830) -- causal by construction, verified directly. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101 6-DoF 30Hz, ALOHA bimanual 14-DoF 50Hz): wins the real safety metric (max instantaneous command jump) 400/400 real trials across both domains. Honest limit, not hidden: average tracking fidelity (RMSE vs the true signal) does not reliably beat a trivial moving median -- this is a safety bound, not a signal cleaner.- 3 new stateful MCP tools (
dense_armor_stream_start/_update/_end,dense_armor.mcp_server): a real-time streaming session wrappingMultiChannelStreamingDeviationDetector-- for a live sensor feed (robot joints, IMU axes) where waiting to collect a full array first isn't an option, unlike the existing batchdense_armor_detect_anomaliestool. Onesession_id-keyed detector instance per session, held in server memory (in-process, same architecture as every other tool here -- see server.py's own docstring); a real caller must call_stream_endwhen a real stream ends, sessions are not garbage-collected automatically. Verified end-to-end against real LeRobot robot-arm joint data fed one point at a time through the real MCP call path (mcp.call_tool), not just the underlying Python function directly.
Fixed¶
utility/curvature.py(curvature): added ascaleparameter (default1.0, reproduces the exact previous behavior --Orca's existing call site unaffected). Real finding, checked directly against real SO-101 joint data (Dense-Evolution-Discovery): the unscaled formula saturates to ~1.0 within ~5 raw units of any reference regardless of the caller's physical units, making it a near-binary near/far indicator rather than a graded proximity signal when used in real degrees (elbow_flex, whose real distance to its real physical limit spans 0.1-66.9 degrees in one real episode: correlation with raw distance only 0.235 at the old fixed scale, vs 0.932 with a physically meaningfulscale=15.0). Not a promotion (no second-domain validation attempted -- this fixes an existing internal utility's own parameterization, not a new one).
[1.1.14]¶
Added¶
utility/streaming.py(StreamingDeviationDetector,MultiChannelStreamingDeviationDetector,classify_segments_ multichannel): porta a latenza zero solo la meta' causale diclassify_segments(arbiter.py) -- il flag di deviazione per-punto, non l'etichetta finale spike/regime, che guardaradiuspunti avanti alla fine di una sequenza deviante e resta una domanda batch/offline per design, non una svista. Buffer semplice (deque, O(span)), non una struttura a due heap -- misurato a ~18.6kHz sostenibile per le finestre gia' usate in tutto il progetto (10-100 punti), oltre 180x il tasso reale di un anello di controllo robotico (30-100Hz). Il supporto multi-canale rimuove il bisogno di un ciclo manuale per-canale (giunti di un braccio robotico, assi di un IMU), ogni canale con la propria finestra di riferimento indipendente. Promosso da Dense-Evolution-Discovery (Esperimenti 48-49) dopo validazione su due domini fisici reali indipendenti (braccio robotico teleoperato SO-101, IMU umano reale UCI HAR) -- stessa disciplina di promozione gia' usata perstable_frame_filter.py. Corrispondenza bit-per-bit conclassify_segmentsverificata direttamente, non assunta.utility/cusum.py(one_sided_arl,two_sided_arl,detectability_report): stima pre-flight, teoria classica Reynolds (1975)/Siegmund (1985), di quanti campioni servono per rilevare uno shift dato il rumore locale reale dicusum_detector-- senza dover lanciare un benchmark prima. Promossa da Dense-Evolution-Discovery dopo validazione su due domini fisici reali indipendenti: lidar (7/7 punti reali, latenza sempre sotto la stima) e accelerometro (5 punti reali, risultato genuinamente misto 2/5 -- non forzato a coincidere con il lidar). L'accelerometro ha anche fatto emergere un limite reale della formula grezza a SNR estremo (ARL frazionario, privo di senso fisico sotto 1 campione) -- corretto qui con un floor a 1.0 su entrambi gli ARL.
[1.1.13]¶
Fixed¶
utility/cusum.py(cusum_detector): il defaulth=5.0("classico da manuale", mai validato contro una lunghezza di serie pratica) dava un tasso di falso allarme a livello di STREAM del 100% (adaptive) / 85% (fixed) su una serie stabile di 1000 campioni -- invisibile al test esistente, che controllava solo il tasso di falso allarme per PUNTO (molto più basso e fuorviante). Trovato come sottoprodotto diretto di un porting parallelo dello stesso algoritmo per the same problem first appeared in an existing online-learning library. Nuovo defaulth=20.0: 3.5% (adaptive) / 15.5% (fixed) sulla stessa serie di 1000 campioni.fixedresta strutturalmente più esposto (il suo riferimento non si aggiorna mai, quindi una stima di warmup sfortunata non si autocorregge) -- non un difetto di taratura, conseguenza diretta di cosafixedè pensato per fare. Nuovo test di regressione (test_default_h_keeps_stream_level_false_alarm_rate_low_ on_a_practical_length) per non ricadere silenziosamente nello stesso problema. Nessun benchmark preregistrato è stato toccato: tutti e quattro (test_benchmark_v0_runtime_behavioral_drift.py,test_benchmark_v2_1_ablation.py,test_benchmark_v2_1_log_ onesided.py,test_benchmark_v2_agent_runtime.py) passanoh=5.0esplicitamente nel proprioCUSUM_KWcongelato, indipendentemente dal default della funzione.
Added¶
utility/stable_frame_filter.py(velocity_gated_stable_mask): filtro che restringe l'analisi di un segnale ai punti dove un segnale di riferimento COMPANION indica uno stato stabile/lento -- evita che un detector interpreti come anomalia un transitorio spiegato da un confondimento noto e indipendente (es. il comando/leader di un braccio robotico che si muove veloce, causando un lag di tracking normale). Parametroalready_rateper riferimenti gia' di tipo velocita' (es. magnitudine giroscopica) invece che posizione -- trovato necessario da un fallimento reale su un secondo caso, non aggiunto preventivamente. Promosso da Dense-Evolution-Discovery dopo validazione su due domini fisici reali indipendenti (braccio robotico teleoperato SO-101, IMU umano reale) -- stessa disciplina di promozione gia' usata perone_sided_upper_filter.
[1.1.12]¶
Added¶
utility/cusum.py(cusum_detector): canale complementare aclassify_segmentsper il drift lento/sostenuto -- CUSUM a due code (Page 1954), modalita'adaptive(default, finestra causale scorrevole, dichiarata onestamente come variante non identica allo schema originale a riferimento fisso) efixed(lo schema classico). Gestione NaN/Inf esplicita. Nato da un gap reale misurato intest/test_benchmark_v0_runtime_behavioral_drift.py:classify_segmentsrileva un glitch transitorio al 100% (latenza 0) ma solo il 9-27% di un drift graduale nella finestra di transizione.utility/one_sided.py(one_sided_upper_filter): filtro a una coda componibile conclassify_segments/cusum_detector-- per segnali dove solo un AUMENTO e' significativo (latenza, error rate). Riduce i falsi positivi misurati su telemetria reale di un agente Qwen2 1.8B (via Ollama) dal 22.5% al 12.5% (classify_segments) e dal 17.5% al 10.0% (cusum_detector), e il falso rifiuto su un cambio di comportamento legittimo dal 24.0% al 4.0% su entrambi -- al costo reale di una sensibilita' ridotta al drift persistente (non nascosto: veditest/test_benchmark_v2_1_ablation.py). Un log-transform provato in parallelo non ha portato benefici misurabili ed e' stato scartato.
Investigated (percorso di validazione, non solo il risultato finale)¶
- Benchmark sintetico congelato (
test/test_benchmark_v0_runtime_ behavioral_drift.py): protocollo preregistrato, 4 condizioni (baseline, drift, glitch, test negativo), mai ritoccato dopo aver visto i risultati. - Benchmark su agente reale (
test/agent_v2/,test/test_benchmark_v2_*): prima validazione non sintetica -- loop minimale con Qwen2 1.8B via Ollama (tool-calling manuale, il modello non restituiscetool_callsstrutturati nonostante il tag "tools"), 4 scenari con ground truth esatta. Trovato: la latenza reale ha una coda molto piu' pesante del rumore gaussiano sintetico, da cui il filtro one-sided sopra.
[1.1.11]¶
Investigated (nessun candidato promosso)¶
compute_damping_gating_smooth(Collatz continuo), mai benchmarkato, ora chiuso: il suo stesso docstring rimandava atest/test_collatz_smooth_experiment.pyper il confronto misurato -- file mai esistito. Scritto ora, giraOrcaper intero sui 7 scenari ditest/testKalman.pyuna volta con il gate costante (default) e una con la variante smooth. Risultato: smooth vince 0/7, pareggia 6/7 (RMSE identico entro 1e-4), perde su "Stealth sub-soglia" (RMSE 0.0029 vs 0.0012, oltre il doppio). Non promosso -- docstring aggiornato con il risultato reale al posto del rimando a un file inesistente.hybrid_shield(phi_ab, gia' vendorizzato per Armatura) come possibile gate per Orca: non e' uno scambio diretto (motore binario a ciclo su tutta la serie, non un gate continuo per elemento), quindi confrontato come pipeline completa (test/test_hybrid_shield_vs_orca_experiment.py) sugli stessi 7 scenari. Risultato misto, non un vincitore netto: vince 4/7 su impulsi isolati/collasso a zero/buco NaN (in 3 casi RMSE ~0.0000, quasi perfetto), ma perde nettamente su segnale sinusoidale strutturato e rumore Cauchy pervasivo (RMSE 2-3x peggiore di Orca) -- ottimo per anomalie isolate, pessimo quando il cambiamento e' diffuso e genuino, non un'anomalia da respingere.pressure_valve(JSD-adattivo, gia' inrobust_filters.py, mai collegato a nessuna pipeline) come terzo candidato: stesso confronto a 7 scenari (test/test_pressure_valve_vs_orca_experiment.py). Vince 1/7 ma in modo netto: RMSE 0.0000 esatto sulla rottura di livello permanente (l'unico dei tre a riconoscere un vero cambio di regime), ma fallisce clamorosamente sugli impulsi isolati (RMSE 86.6 su "impulsi alternati", peggio di non fare nulla). Causa reale trovata (non un'ipotesi): con 3 picchi ravvicinati in una finestra di 21 punti, mediana/IQR e mediana/MAD restano ESATTAMENTE 0 (18/20 punti identici, matematicamente corretto per stimatori con breakdown ~25-50%), quindi vengono scartati e resta solo media/std -- non robusta, contaminata dagli stessi picchi che dovrebbe rilevare. Fix parziale applicato: la finestra locale ora esclude il punto stesso (come gia' faceva solo il sotto-passo sigma-clipping), corretto in linea di principio ma verificato non sufficiente quando PIU' outlier restano comunque vicini tra loro nella stessa finestra (nessun cambiamento misurabile sui 7 scenari). Un M-estimator di Huber (IRLS, vedi Sung et al. 2019, arXiv:1912.04982, ricerca bibliografica reale via RAG locale) e' stato provato come alternativa piu' sofisticata e converge ANCH'ESSO a scala 0, per lo stesso motivo di fondo (la sua scala e' a sua volta una MAD dei residui). Conclusione onesta: non e' un problema di scelta dell'estimatore, e' che dividere per una scala locale legittimamente zero (baseline quasi piatta) e' indefinito per costruzione, qualunque stimatore si usi. Non risolto -- vedi il "LIMITE NOTO" nel docstring dipressure_valveper la via di fix non ancora tentata (scala di fallback dalla finestra di riferimento piu' ampia).- Conclusione dei tre esperimenti: nessuno dei tre candidati batte in modo netto il gate costante 0.85 di Orca su tutti gli scenari -- ognuno (incluso il default) ha una nicchia precisa (impulsi isolati, segnale diffuso/rumore pervasivo, cambio di regime permanente). Il gate costante resta quello di default; nessuna promozione fatta.
Fixed¶
core/vector.py,run_parallel_scenariosricompilava ad ogni chiamata:jax.jit(jax.vmap(...))veniva ricostruito DENTRO il metodo ad ogni invocazione -- una nuova closure Python ad ogni chiamata, quindi un nuovo oggettojax.jitla cui cache di compilazione non poteva mai fare hit con la precedente. Il "warm run" restava bloccato a ~130ms invece di scendere ai pochi ms attesi (la diagnosi originale, un report Colab, puntava al castnp.arraycome colpevole -- verificato e smentito: il vero costo era la ricompilazione ad ogni chiamata). Fix: motore jit+vmap spostato a livello di modulo, costruito una sola volta;base_statepassato come argomento tracciato invece che chiuso in closure, cosi' valori diversi non forzano una nuova compilazione. Verificato: da un plateau di ~140ms/chiamata a ~2.4ms/chiamata (~55x) su chiamate ripetute al metodo pubblico.utility/orca.py, riferimento pulito che attraversa lo zero esplodeva la compressione log10: un valore pulito quasi-zero (anche solo un residuo di floating point, mai esattamente0.0-- es.sin(4π) ≈ -4.9e-16) faceva esplodere il fattore di scala condivisofact_shared. Siccome lo stesso fattore si applica anche al corrotto (che NON e' vicino a zero), il suo controvalore compresso schizzava a ordini di grandezza sopra i punti vicini (misurato: da~1e-4tipico a~2e9in un caso), sballando la memoria causale diAdaptiveSignalStabilizerper l'intero batch, non solo per quel punto. Fix a due livelli: (1) l'esponente non viene piu' amplificato oltre la scala target (val_e); (2) il valore compresso del corrotto resta comunque limitato a un multiplo ampio (20x) della scala target, indipendentemente dalla causa esatta del salto. Verificato: MSE su un riferimento sinusoidale che attraversa lo zero da ~9 (peggio della modalita' cieca sullo stesso segnale) a ~0.02.
Changed¶
core/engine.py, naming e precisione non armonizzati col resto della libreria: le variabili interne diAdaptiveSignalStabilizer(_step_kernele la variante storica_legacy_step) si chiamavanophi_ab/v_dyn/trigger-- stessi nomi dicore/hybrid_engine.py(motore di Armatura) per una formula matematicamente diversa. Rinominate in modo non ambiguo (coherence_signal/coherence_mixecoherence_legacy/dynamic_shift_legacy/is_anomaly_legacy). Il file usava anchefloat32esplicito ovunque, mentre il resto della libreria richiedejax_enable_x64e gira infloat64-- i commenti giustificavano ilfloat32con una "compatibilita' di esportazione binaria nativa" che non esiste da nessuna parte nel repo (verificato con una ricerca sull'intero codebase). Convertito tutto afloat64, rimossi i commenti fuorvianti. Suite completa (189 test, inclusi i 5 file adversarialtest_bound*.py) invariata prima e dopo entrambe le modifiche.
Added¶
Orcausa oraUniversalMemoryGuardinvece di un check RAM ad-hoc:_gc_se_ram_bassa()duplicava a mano un checkpsutilgia' fatto meglio dacore/memory.py(che copre anche la VRAM, mai controllata prima). Cambio di comportamento onesto: sotto la soglia critica ora sollevaMemoryPressureErrorinvece di continuare in silenzio -- fail-fast con un errore leggibile invece di un OOM qualche chunk piu' avanti.Orcaricorda riferimenti puliti passati e li richiama viaapply_fast_resonance: in modalita' cieca (x_reference=None), prima di ricadere sulla stima locale (_blind_reference), Orca cerca nella propria memoria (per shape, fino areference_memory_size=32per default) un riferimento gia' visto simile all'input corrotto corrente (sogliareference_recall_min_score= 0.90, calibrata empiricamente: segnali correlati restano sopra 0.95 anche con rumore moderato, segnali scorrelati non superano ~0.75). Banca vuota o nessun match abbastanza simile -> comportamento identico a prima.chunk_thresholddiOrcaauto-tarato suAIHardwareProfiler: default cambiato da un intero fisso (1_000_000, uguale per qualunque host) aNone, che auto-deriva il valore scalando proporzionalmente amax_tensor_dimdell'host corrente (RAM/GPU-TPU). Su un host "base" (RAM<12GB, nessuna GPU/TPU) il risultato e' identico al vecchio default fisso -- nessuna regressione silenziosa sul caso comune. Un valore esplicito bypassa l'auto-tuning senza nemmeno istanziare il profiler.Orca(use_arbiter=True), opzione nuova, defaultFalse: instrada ogni punto verso il correttore giusto invece di forzarne uno solo su tutta la riga -- nato dai tre esperimenti sopra (nessun gate unico batteva gli altri due ovunque). Nuovo moduloutility/arbiter.py: classifica ogni punto come 'clean'/'spike'/'regime' confrontandolo con una finestra di riferimento larga e CAUSALE (solo punti prima di i, mai dopo -- stessa scala della "molla" JSD dipressure_valve, non la finestra locale stretta che si e' vista collassare sopra), poi la lunghezza e la coerenza interna della sequenza di punti devianti decide impulso isolato vs cambio di regime sostenuto. 'spike' -> rigetto duro (mediana della finestra larga); 'regime' -> valore grezzo (fiducia, stessa filosofia del gate costante); 'clean' -> quello che lo scudo standard a 4 fasi ha gia' prodotto (il suo smorzamento morbido, non un pass-through, altrimenti un segnale continuo non anomalo come un'oscillazione strutturata verrebbe lasciato passare intatto). Verificato su tutti i 7 scenari ditest/testKalman.py(test/test_arbiter_orca_integration.py): mai peggiore del default (use_arbiter=False, garanzia testata), migliore su 5/7 (impulsi isolati, sub-soglia, Cauchy, rottura di livello, buco NaN), pari sul collasso a zero, identico sul segnale strutturato (ricade correttamente sullo scudo standard, com'e' giusto: e' smorzamento continuo, non un'anomalia discreta). Popola ancheOrca.etichette_arbitro/incertezza_arbitro(l'incertezza della classificazione, non la dimensione della correzione -- segnale complementare amargine_ingresso, non ridondante) eOrca.tipi_corruzione_visti(slice_shape), un Counter dei tipi di corruzione gia' osservati per quella shape quandox_referenceera noto -- memoria separata dalla banca dei riferimenti puliti gia' esistente, che ricordava solo la FORMA, non il TIPO di corruzione. CRONOLOGIA di un limite trovato e risolto nella stessa sessione: la prima versione usava una finestra di riferimento SIMMETRICA, che a cavallo di una rottura di livello permanente mescolava vecchio e nuovo livello nella propria scala, non rilevando affatto la transizione (deviazione sempre 0) -- un primo confronto isolato aveva scambiato questo per un successo (RMSE 0.0), ma era un artefatto: i dati grezzi di quello scenario di test coincidono GIA' col target, quindi "passa tutto grezzo" da' RMSE 0 indipendentemente dall'etichetta. Attraverso Orca la mancata rilevazione si vedeva davvero: nessun miglioramento (9.4007 identico con o senza l'arbitro). Passando alla finestra causale, la rilevazione e' diventata reale (indici 60-74 etichettati 'regime', esattamente la transizione vera a 60) e l'RMSE reale scende a 6.0449 -- vediutility/arbiter.pyper il dettaglio completo.dense_armor.mcp_server, nuovo (extra[mcp]): server MCP con 5 tool (dense_armor_health,dense_armor_clean_signal,dense_armor_detect_anomalies,dense_armor_robust_filter,dense_armor_heal_series) cosi' un agente puo' ripulire una serie senza scrivere Python. Diretto e in-process, non un proxy HTTP verso un kernel separato come l'adattatore di Dense-Evolution -- Dense-Armor non ha una web UI da condividere. Vive apposta sottodense_armor.mcp_server, non un semplicemcp_server: con Dense-Evolution installato nello stesso ambiente (il suo adattatore si chiama esattamentemcp_server), un nome non annidato e' una collisione VERA, osservata direttamente (from mcp_server.server import mcprisolveva ai 25 tool di Dense-Evolution invece dei 5 di qui, a seconda dell'ordine di installazione), non un'ipotesi -- scoperta e corretta prima che questo finisse pubblicato. Nuovo script consoledense-armor-mcp, separato dadense-armor(la CLI di Armatura, invariata). Un bug reale trovato e corretto nello sviluppo: lo schema Pydantic divaluesdichiarava supporto NaN nel docstring ma rifiutava silenziosamentenullJSON dentro la lista (List[float]invece diList[Optional[float]]). Verificato end-to-end viamcp.call_tool()reale (non solo le funzioni Python dirette) intest/test_mcp_server.py, 222/222 test verdi in totale.
[1.1.9]¶
Fixed¶
core/compiler.pyricompilava ad ogni chiamata:run_dynamic_pipeline,run_pipeline_with_chunkingecompute_gradientsdefinivano il proprio kernel@jax.jitDENTRO il metodo -- un nuovo oggettojax.jit(quindi una cache di compilazione vuota) ad ogni singola chiamata, mai riuso della compilazione XLA anche a parita' di shape tra una chiamata e la successiva. Stesso bug gia' trovato e risolto nel motore diAdaptiveSignalStabilizer(vedi [1.0.3]), mai corretto qui. Trovato scrivendo un test che doveva dimostrare l'utilita' reale diPipelineProfiler(warm-up separato dal regime): con il bug, warm-up e regime erano praticamente identici. Fix: i tre kernel spostati a livello di modulo, compilati una sola volta. Verificato: 375ms -> 0.2ms a regime sulla stessa pipeline (>1700x), test di regressione aggiunto.utility/anwav.pyandava in crash su console Windows non-UTF-8: i verdetti su volume "troppo spinto"/"troppo silenzioso"/picco a rischio usavano emoji (❌,⚠️) neiprint()-- su cp1252 (l'encoding di default della console Windows) sollevavanoUnicodeEncodeError, quindi qualunque uso reale della funzione fuori da pytest (che forza UTF-8 intest/conftest.py) su un file rumoroso o silenzioso andava in crash. Rimosse le emoji dai messaggi.- Link rotto in questo stesso CHANGELOG bloccava il deploy del sito in silenzio: la voce
1.1.8 linkava
[Toolkit](../api/toolkit.md), un percorso relativo valido solo dentrodocs/-- maCHANGELOG.mdvive nella root del repo e viene incluso cosi' com'e' indocs/changelog.md.mkdocs build --strictrifiutava giustamente la build, e i due push precedenti non erano mai arrivati sul sito pubblico (la sezione toolkit digetting-started.mde gli esempi indocs/api/toolkit.mderano gia' su GitHub ma non ancora online). Corretto con l'URL completo del sito; verificato dal vivo che il sito pubblico ora li mostra davvero.
Changed¶
docs/api/toolkit.mdnon vende piu' ogni modulo allo stesso modo: aggiunte note oneste dove l'utilita' e' effettivamente sottile (TensorVaultsono matrici scrivibili in una riga, l'euristica RAM diAIHardwareProfilere' arbitraria,BitwisePermutationEnginefa un solo swap controllato per chiamata non una permutazione generale, l'update diParametricScenarioSimulatore' una EMA fissa non un modello configurabile, i formatter di logging sono wrapper sottili,StochasticAdversarialNoisesi sovrappone alla suite adversarial reale invece di aggiungerci qualcosa) e rafforzate quelle con prove reali (PipelineProfilerha scoperto il bug sopra; i preset producono filtraggio misurabilmente diverso;kappainapply_fast_resonancemodula davvero il punteggio, non e' cosine similarity travestita).
Added¶
- Test che dimostrano l'utilita' dichiarata dei moduli, non solo che "non crashano": preset
diversi (
SIGNAL_STABILIZER_PRESETS) producono un filtraggio misurabilmente diverso quando collegati aAdaptiveSignalStabilizer;kappainapply_fast_resonancemodula davvero il punteggio; il warm-up diPipelineProfilere' davvero piu' lento del regime (il test che ha scoperto il bug di ricompilazione sopra). docs/api/toolkit.md: ogni modulo del toolkit ha ora un esempio di codice reale verificato (non solo la firma auto-generata dai docstring), preceduto da cosa fa e a cosa serve.
[1.1.8]¶
Fixed¶
docs/getting-started.mdnon spiegava come usare il toolkit: la pagina aveva un quickstart per Orca, Armatura e robust_filters, ma per il toolkit (14 moduli, aggiunti in 1.1.6-1.1.7) solo un link nudo nella home del sito, nessun esempio eseguibile. Aggiunta una sezione "Standalone toolkit" con due esempi reali verificati (DynamicAICodegen,UniversalMemoryGuard) e link a Toolkit per il resto.
[1.1.7]¶
Nessuna modifica alla matematica/logica di Armatura/Orca in questa versione. I 14 moduli
del toolkit aggiunti a 1.1.6 sono ora davvero pubblici (documentati sul sito, show_source:
true), quindi tutto quello che c'era di poco professionale nel codice sorgente -- non solo nel
README -- ora si vede.
Added¶
- Sito: nuova pagina
docs/api/toolkit.md(mkdocstrings, pesca dai docstring reali) per i 14 moduli aggiunti in 1.1.6 -- prima invisibili sul sito, l'API Reference copriva solo i 5 moduli dello scudo. Linkata damkdocs.yml,docs/index.md,docs/api/index.md. - README: la sezione
$ toolkit --standalone(prima solo un blocco di import con commenti) riscritta con prosa reale, raggruppata per categoria (pipeline/chunking, hardware/profiling, tensori/configurazione, logging/provenance, audio/I/O dati, ricerca per similarità).
Changed¶
- Rimosso "Sentinel"/branding residuo dal codice sorgente dei 14 moduli toolkit e dei 5
moduli originali dello scudo (
armatura.py,core/damping_operator.py,utility/metro.py,utility/orca.py-- mancati nel passaggio di de-branding di 1.1.5, che si era fermato al testo rivolto all'esterno README/pyproject/sito, senza scendere nei singoli docstring conshow_source: true): banner "SENTINEL ENTERPRISE... BEAST MODE" e claim falsi di "logica quantistica" incore/chunk.py, blocco "Fix applicati/BILANCIAMENTO AUREO" (contenuto da changelog finito nel docstring) incore/compiler.py, riferimenti a file interni inesistenti (simulator.py,NoiseModel,test21.py/main.py) incore/vector.py/core/noise.py/core/chunk.py, intestazioni "Sentinel Metrology Framework" e vecchio percorso internoshield_/...nei 4 moduli originali. - Simboli pubblici rinominati (uniche API realmente rotte da questa release, erano appena
diventate pubbliche in 1.1.6):
execute_pipeline_beast_mode->execute_pipeline_chunked(core/chunk.py);SENTINEL_PRESETS->SIGNAL_STABILIZER_PRESETS(core/preset.py);get_enterprise_logger->get_json_file_logger, nome logger/file di defaultsentinel*/sentinel_dashboard.log->dense_armor*/dense_armor.log, campo JSON"framework": "Sentinel-TensorFlowEngine"->"dense-armor"(core/logger.py);AIEngineVisualizer.ENGINE_SIGNATURE/intestazione del trend report non dicono piùTensorFlowEngine-Sentinel(core/visualizer.py). utility/orca.py: rimossa unasys.path.insert(...)morta (residuo di quando il modulo girava come script standalone fuori dal pacchetto -- gli import relativi non ne hanno bisogno, e nient'altro nel file usavasys/osdopo quella riga) e i relativi import inutilizzati.armatura.py: docstring e messaggi di uso CLI aggiornati dapython armatura.py/from armatura import Armatura(invocazione da script standalone) apython -m dense_armor/from dense_armor import Armatura(pacchetto installato).
[1.1.6]¶
Nessuna modifica alla matematica/logica di Armatura/Orca in questa versione. Partita
dalla richiesta di portare la coverage Codecov al 100%: 14 file in core//utility/
risultavano a 0% di coverage. Prima di cancellarli come "codice morto" (la prima ipotesi),
trovato dense_armor/README.md (riferimento interno) che li documenta tutti come componenti
reali e intenzionali — indagine invertita, modulo per modulo, invece di procedere con la
cancellazione.
Fixed¶
core/__init__.pyvuoto: il vero file di inizializzazione del pacchetto (import/export di 9 classi:AIHardwareProfiler,StochasticAdversarialNoise,UniversalMemoryGuard,MemoryPressureError,ParametricScenarioSimulator,BitwisePermutationEngine,DynamicAICodegen,CMD_MAP,TensorVault,AIEngineVisualizer,PipelineProfiler) esisteva ma era salvato comecore/init.py(nome sbagliato, mai eseguito da Python) — il file col nome giusto era vuoto. Nessuna di queste classi era importabile dadense_armor.core.*nonostante il codice reale esistesse. Contenuto spostato nel file col nome corretto,init.pyeliminato.core/preset.py: l'intero dizionarioSENTINEL_PRESETS(4 profili calibrati) era definito due volte verbatim nello stesso file. Rimossa la duplicazione.utility/iodat.py:lodat()ritornava in silenzio un tensore di dati casuali finti se il file richiesto non esisteva (solo un log di warning, nessun errore) — ora sollevaFileNotFoundError.
Added¶
- Test reali per tutti e 14 i moduli precedentemente a 0% di coverage:
chunk.py,compiler.py,memory.py,preset.py,logger.py,tensor.py,vector.py,noise.py,profiler.py,visualizer.py(core/);anwav.py,diagnostic.py,iodat.py(utility/, con file WAV/HDF5/NetCDF reali generati nei test, non mock) — più le righe ancora scoperte inresonance_search.py. Coverage totale del pacchetto: 54% -> 91%. - README: nuova sezione
$ toolkit --standaloneche documenta questi moduli come seconda parte del pacchetto, indipendente dallo scudo anomalie di Armatura/Orca.
[1.1.5]¶
Nessuna modifica alla matematica/logica di Armatura/Orca/robust_filters in questa
versione -- solo infrastruttura di test, documentazione e metadati.
Added¶
- Suite di test adversarial reale (
test/test_boundA-E.py): i test dietro la tabella "robustezza adversarial" del README (PGD/BIM/MI-FGSM, affine/elastica, Carlini-Wagner L2/Linf, DeepFool, Fourier a banda estesa) esistevano solo in un progetto precursore ("sentinel3", mai parte di questo repository) da cuiAdaptiveSignalStabilizerera stato estratto. Riportati qui con una modifica sostanziale: l'import punta ora adense_armor.core.engine.AdaptiveSignalStabilizer(il pacchetto reale) invece del modulo locale standalone del progetto precursore -- stessa classe, stessa API, ri-eseguiti e confermati riprodurre esattamente i numeri gia' nel README (PGD 0.0130, BIM 0.0625, MI-FGSM 0.0784, C&W L2 78.96%, C&W Linf 64.39%, DeepFool 78.79%, Fourier 99.77%+): la tabella e' ora una misura reale e riproducibile, non piu' un dato non tracciato. - Sito di documentazione (GitHub Pages, https://tatopenn-cell.github.io/Dense-Armor/):
home, guida rapida, riferimento API generato dai docstring reali dei 5 moduli pubblici
(
Armatura,Orca,hybrid_engine,engine,robust_filters), changelog/licenza sorgentati daCHANGELOG.md/LICENSE.md-- stesso impianto mkdocs-material gia' in uso su Dense-Evolution.
Fixed¶
- Due assert instabili in CI:
test_boundB.py/test_boundD.pyavevano unassert t_elapsed >= 5.0che non verificava la correttezza della difesa, solo che il calcolo fosse durato "abbastanza" sulla macchina di sviluppo originale -- falliva su ogni runner CI piu' veloce (verificato: 4.41s invece dei 5.0s richiesti su GitHub Actions, riprodotto su ubuntu/windows-latest, ogni versione Python). Rimosso: le assert reali (nessuna divergenza numerica) sono l'unica verifica che conta.
Changed¶
- Rimosso "Sentinel" (nome interno originale del progetto) dal testo rivolto
all'esterno: descrizione PyPI (
pyproject.toml), README, home della documentazione, docstring di__init__.py, e l'etichetta nella definizione di Software inLICENSE.md(solo l'etichetta, nessuna modifica ai componenti elencati/coperti dalla licenza). IlCHANGELOG.md(storia reale del progetto) e i commenti interni nel codice restano invariati.
[1.1.4]¶
Added¶
pressure_valve: orchestratore automatico dei quattro rilevatori aggiunti in 1.1.3. Non un voto (quanti metodi segnalano un punto) ma la combinazione classica a minima varianza (stimatore BLUE): ogni metodo produce una coppia (centro, scala) locale, i pesi della combinazione sono derivati con un moltiplicatore di Lagrange minimizzando la varianza della combinazione pesata sotto il vincolo che i pesi sommino a 1 -- w_k proporzionale a 1/scala_k^2, un metodo la cui incertezza si gonfia (es. Chauvenet quando la finestra contiene gia' un outlier) viene automaticamente pesato meno, senza scartarlo esplicitamente. Decisione finale sempre binaria (marcato/sostituito con la mediana locale, o intatto). Soglia di default (soglia_pressione=8.0) calibrata empiricamente -- 1/300 falsi positivi su rumore gaussiano puro, rilevamento corretto di un outlier chiaro e di due outlier ravvicinati.- Soglia dinamica via JSD ("la molla"):
soglia_pressionenon e' piu' fissa -- si confronta la finestra locale con una piu' ampia (Jensen- Shannon divergence) e si allarga proporzionalmente quando le due distribuzioni divergono (una vera transizione di regime in corso), senza mai smettere di decidere in binario. La soglia puo' solo allargarsi (JSD >= 0), mai restringersi sotto il valore base. - Bug reale trovato e corretto durante la calibrazione: la JSD "ingenua" (bin fissi, epsilon quasi-zero) risultava PIU' alta su rumore stazionario puro che vicino a una vera transizione -- il contrario esatto dello scopo del meccanismo, un artefatto da pochi campioni per bin. Corretto con binning adattivo (~6 campioni/bin) e smoothing di Laplace vero (pseudo-conteggio, non solo un epsilon per evitare log(0)) -- riverificato: JSD media vicino a una vera transizione 1.0->5.0 ora 2-3x piu' alta che su rumore stazionario alla stessa ampiezza, la direzione corretta.
[1.1.3]¶
Added¶
dense_armor/utility/robust_filters.py: quattro rilevatori di anomalie "a basso costo" -- Chauvenet's criterion (1863), Tukey's fences/IQR, Hampel filter, sigma-clipping iterativo. Statistiche classiche, note e usate da decenni in telemetria/astronomia, nessun modello dinamico/stato -- solo aritmetica su una finestra locale centrata (pensate per pulizia offline/ batch, non il ciclo causale real-time dicore/hybrid_engine.py; adatte anche a un futuro porting embedded/Arduino, dove non ci si potra' appoggiare a dipendenze come numpy).pressure_valve: orchestratore automatico dei quattro metodi sopra. Non un voto (quanti metodi segnalano un punto) ma la combinazione classica a minima varianza (stimatore BLUE): ogni metodo produce una coppia (centro, scala) locale, e i pesi della combinazione sono derivati con un moltiplicatore di Lagrange minimizzando la varianza della combinazione pesata sotto il vincolo che i pesi sommino a 1 -- il risultato, w_k proporzionale a 1/scala_k^2, pesa automaticamente meno un metodo la cui incertezza si gonfia (es. Chauvenet quando la finestra contiene gia' un outlier), senza doverlo scartare esplicitamente. Decisione finale sempre binaria (marcato/sostituito con la mediana locale, o intatto) -- nessuno stato intermedio, coerente col resto dell'ecosistema. Soglia di default (soglia_pressione=8.0) calibrata empiricamente (non "3 sigma": la scala combinata e' sempre piu' stretta di ogni scala singola) -- verificato 1/300 falsi positivi su rumore gaussiano puro, rilevamento corretto di un outlier chiaro e di due outlier ravvicinati (dove Chauvenet da solo soffre), nessun tocco su un gradino genuino sostenuto.
[1.1.2]¶
Fixed¶
hybrid_shieldrestava bloccato su un equilibrio sbagliato dopo un cambiamento di livello genuino e sostenuto (core/hybrid_engine.py): trovato testandoArmatura(livello_ia=0.0)dal vivo su uno scenario base (salto da 1.0 a 5.0, poi 30 punti stabili al nuovo livello). La finestra di baseline locale era presa daout(già guarito) invece che daprocessed(grezzo) — una scelta deliberata per proteggere da uno spike isolato passato che sposta la baseline, ma con un effetto collaterale peggiore su un gradino vero: i primi 1-2 punti dopo la transizione vengono inevitabilmente respinti (nessun rilevatore causale distingue un gradino vero da uno spike al primo campione), quei punti respinti finiscono inout, e la finestra dei punti successivi li rimedia dentro il proprio calcolo — producendo una baseline "a metà strada" che respinge ANCHE i punti successivi genuini, in un loop che si autoalimenta. Il segnale restava bloccato per sempre su un valore intermedio sbagliato (~4.0 invece di 5.0), non solo per una manciata di passi transitori. Fix: la finestra torna a usareprocessed(grezzo), riallineata aia_utils.vector_healing.enhanced_dense_healing_hybrid(il riferimento che questo modulo dichiara di generalizzare) — il valore di fallback è ora la MEDIANA della finestra grezza, non la media: la mediana ignora già da sola un singolo spike isolato nella finestra, quindi la protezione originale resta valida senza bisogno del truccoout(riverificato direttamente). Il test di regressione esistente (test_gradino_sostenuto_non_viene_appiattito_ma_lo_spike_isolato_si) non lo intercettava (tolleranza/finestra di controllo troppo larghe, passava anche col bug) — aggiunto un nuovo test più severo che fallisce sul codice vecchio e passa su quello corretto.
[1.1.1]¶
Fixed¶
- NaN sanato con la media globale della serie invece di una stima locale
(
core/hybrid_engine.py,armatura.py): trovato testando l'esempio esatto del README con un'installazione pulita da PyPI subito dopo la 1.1.0. UnNaNveniva sostituito connp.nanmeancalcolata su TUTTA la serie — se uno spike enorme era presente ovunque nella serie (anche lontanissimo), contaminava il sostituto di ogni NaN, non solo di quelli vicini allo spike. Su serie brevi il sostituto risultava assurdo (2000.81 invece di ~1.28 sull'esempio del README, n=6); su serie più lunghe l'effetto si attenuava ma non spariva (51.24 invece di ~1.0 su n=200) — non era un caso limite di serie corte, era la logica stessa. Se il NaN cadeva anche entro il raggio locale di uno spike (fino a 20 passi), la volatilità locale gonfiata dallo spike impediva pure al trigger di ricorreggerlo verso la baseline. Fix: nuova funzione_local_nan_fill— ogni NaN è sostituito con la mediana dei vicini FINITI in una finestra locale (mediana, non media: robusta a uno spike che capiti nella stessa finestra), non con una statistica sull'intera serie. La marcatura come anomalia (anomalie) era già sempre corretta anche col bug — solo il valore restituito inpulitoper quel punto era sbagliato.
[1.1.0]¶
Changed¶
- Motore di
Armaturasostituito:Armatura.analizza()non usa piùAdaptiveSignalStabilizer(Stadio 1) +ABCollatz(Stadio 2). Il gate ABCollatz era già documentato in [1.0.10] come matematicamente non discriminante (il radicale di una traiettoria di Collatz non ha relazione monotona con l'intensità del rumore — verificato con sweep numerico, e una sigmoide monotona alternativa, pur costruita e testata, peggiorava sistematicamente su 7 scenari reali). Sostituito con un motore a trigger binario (dense_armor/core/hybrid_engine.py), portato e adattato dalla logica phi_ab/vettore-dinamico/trigger già verificata in Dense-Evolution (dense_evolution/healing.py, oggi in produzione su PyPI dense-evolution= 8.1.9) e in
ia_utils.vector_healing.enhanced_dense_healing_hybrid. Tre adattamenti necessari per il nuovo dominio (serie scalari a scala libera, non vettori di embedding normalizzati), tutti verificati con test manuali mirati prima di essere accettati: (1) la baseline della finestra locale usa i valori già guariti (out), non quelli grezzi, altrimenti un outlier passato continua a spostare la baseline perradiuspassi dopo di sé; (2) l'IPG (gradiente istantaneo) resta sui valori grezzi, non su quelli guariti, altrimenti un punto respinto non lascia mai più traccia e nessun gradino reale può essere riconosciuto in seguito; (3) la distanza tra stati è normalizzata sulla volatilità locale del segnale (deviazione standard delle differenze successive nella finestra), non su una costante fissa né sulla grandezza assoluta dei valori — altrimenti o qualunque cambiamento sopra ~1.4 unità restava permanentemente appiattito, o (nel tentativo intermedio) anche uno spike isolato passava intatto perché "proporzionalmente coerente" con la propria grandezza. Contratto pubblico diArmaturainvariato (analizza,referto,referto_json,deriva, stessa firma del costruttore);Kè ora binario (0.0/1.0) invece di una sigmoide continua, coerente con la natura binaria del trigger sottostante. Orcanon è stata toccata in questa release: continua a usareAdaptiveSignalStabilizer+ABCollatz+apply_damping_blend. I numeri di robustezza adversarial nel README (PGD/Carlini-Wagner/Fourier) restano validi e non ricalcolati, perché misurati su quel motore.
[1.0.11]¶
Fixed¶
K_anomalousinapply_damping_blend(dense_armor/core/damping_operator.py): la formulac_anom/(c_anom+diff)dava K_anomalous massimo a differenza quasi nulla (segnale già pulito, dovrebbe damparsi poco) e lo faceva decrescere fino al pavimento_ALPHA=0.25al crescere della differenza — l'opposto di quanto dichiarato nella docstring della funzione. Invertito il rapporto (diff/(c_anom+diff)). Testato su 30 seed × 4 livelli di rumore: il fix batte l'originale 30/30 a rumore medio/alto/molto alto (RMSE fino a -0.137), ma peggiora sistematicamente a rumore molto basso (limite noto, documentato e coperto da test di regressione dedicato).
Added¶
dense_armor/utility/healing.py—healing_filter: nuovo modulo, a sé stante (non integrato inarmatura.py/orca.py), porting concettuale daDense-Evolution/dense_evolution/healing.py. A differenza di ABCollatz e del damping Stadio 1 (giudicano un punto dal proprio residuo istantaneo), classifica ogni punto guardando quanti vicini condividono la stessa deviazione dalla baseline locale — deviazione isolata = rumore (sostituita con la mediana di una finestra più ampia), deviazione sostenuta dai vicini = cambiamento vero (lasciata passare). Batte una mediana mobile a raggio 2 su segnali con transizioni vere + spike (40/40 e 30/30 su più varianti testate); perde su denoising puro di segnali stazionari a basso/medio rumore (limite noto, non è il suo caso d'uso primario).
[1.0.10]¶
Fixed¶
- Gate ABCollatz dello Stadio 2 (
compute_damping_gating): era matematicamente saturo a ~0.85 sempre, indipendentemente dal rumore vero — causa: il radicale di un intero (derivato dalla traiettoria di Collatz) non ha nessuna relazione monotona con la sua grandezza (verificato con sweep numerico: rumore crescente 0->1000 dava discrepanze 0, 300, 220, 600, -8, 13920, 0, senza andamento). Non era un problema di scala/compressione: l'artefatto restava identico su dati grezzi. - Costruita e testata anche una sigmoide monotona e scala-invariante sul
rumore relativo, genuinamente discriminante — ma su 7 scenari reali
(
test/testKalman.py), sia in modalità cieca sia con riferimento vero esplicito, peggiora sistematicamente l'RMSE rispetto al fallback costante 0.85 (anche alzando il pavimento minimo fino a 0.84). Nel design attuale di Orca il riferimento (Stadio 1) è già una stima affidabile: correggere sempre con forza verso di esso batte qualunque discriminazione basata sul rumore locale. Ripristinato il fallback costante, ora dichiarato esplicitamente invece di emergere per accidente da una formula rotta.
Nessun cambio di comportamento a runtime per chi già usa il pacchetto (RMSE e tempi verificati pressoché identici prima/dopo su tutti gli scenari di test) — il fix è di correttezza/manutenibilità del codice, non di funzionalità.
[1.0.9]¶
Indagine approfondita partita dalla verifica di uno script esterno che testava (male, con una reimplementazione a mano non fedele) il fix 1.0.8 — ha portato a scoprire e risolvere 4 bug distinti in cascata nello scudo entrata, ciascuno mascherato dal precedente.
Fixed¶
- Compressione log10 (
Orca._execute_4_phase_input_shield): due fattori di scala indipendenti (uno da pulito, uno da corrotto) collassavano qualunque valore alla stessa magnitudine compressa, distruggendo ogni differenza relativa prima ancora che Stadio 1/Stadio 2 la vedessero (verificato: anche un pass-through totale senza nessuna vera protezione ricostruiva il pulito esatto, allo stesso modo). Ora un solo fattore condiviso, derivato dal pulito, applicato a entrambi. - Stadio 2 ingannato dal segnale già ammortizzato:
compute_damping_gatingvalutavaf1(output dello Stadio 1) invece del segnale originale, sotto-stimando l'anomalia se già parzialmente corretta a monte. - Contaminazione post-shock dello Stadio 1: il motore ricorsivo
(
AdaptiveSignalStabilizer) lasciava sempre passare almeno il 25% (k_anom_min) di un'anomalia enorme nel proprio stato interno, che poi decadeva lentamente contaminando per diversi passi anche campioni successivi perfettamente normali. - Costante
c_anomfissa non in scala con i dati compressi: era comparabile in grandezza al rumore in spazio compresso, impedendo alla soppressione naturale delle anomalie di funzionare anche con lo State Flush attivo.
Added¶
- Hard-clamp deterministico:
raw_noise(dati grezzi originali, prima di qualunque compressione) > 0.05 forza il gate finale a 0.99 (non più 0.85, per non ereditare il pavimento pensato per i disturbi ordinari). - State Flush: lo stesso segnale hard-clamp, già autorevole, passato
anche allo Stadio 1 (nuovo parametro opzionale
hard_clamp_masksufilter_batch_scenarios/_process_single_scenario/_step_kernel) — azzera il pavimento di guadagno minimo solo per il passo flaggato. c_anomscala-adattiva: proporzionale alla magnitudine locale corrente (prev_filtered, già nello stato ricorsivo) invece di una costante fissa assoluta — si adatta da sola sia a dati grezzi (Armatura,filter_data_streamdiretto) sia a dati compressi (Orca), senza imporre l'assunzione di scala di un solo chiamante nella classe genericaAdaptiveSignalStabilizer.
Verificato con numeri reali: un outlier da 9999 in una serie di valori ~1.3 è ora protetto a 3.61 (indistinguibile dagli altri campioni ~3.4-3.62 dopo il modello), tutti i vicini tornano normali, margine d'errore corretto. Tutti i parametri nuovi sono opzionali con default che preservano il comportamento esistente per chi non li usa.
[1.0.8]¶
Fixed¶
Orca._execute_4_phase_input_shield: lo Stadio 2 (gating ABC/Collatz) valutavaf1, l'output già ammortizzato dallo Stadio 1 (AdaptiveSignalStabilizer), invece del segnale originale — se lo Stadio 1 riduceva parzialmente un outlier enorme, lo Stadio 2 poteva sotto-stimare quanto fosse anomalo l'input reale. Ora valuta il segnale compresso pre-Damping.
Added¶
- Sbarramento deterministico (hard-clamping): la soglia di rumore critico
(0.05) è calcolata sui dati grezzi originali, prima di qualunque
compressione log10 (che rinormalizzerebbe ogni valore individualmente,
facendo perdere l'intensità reale del rumore). Se superata, il gate
finale viene forzato alla blindatura massima (0.85), bypassando il
calcolo ABC/Collatz solo per le macro-anomalie; sotto soglia la
pipeline sinergica originale resta invariata. Verificato: il leak
residuo su una macro-anomalia crolla a zero.
@jax.jit/jax.vmapintatti, nessuna nuova chiamata eager fuori dal kernel precompilato.
[1.0.7]¶
Fixed¶
evaluate_abc_discrepancyarrotondava semprebprima di calcolare il radicale, anche quando chiamata dacompute_damping_gating_smooth— che genera apposta valoricollatz_wavenon arrotondati tramiteexecute_collatz_step_smooth. La variante smooth collassava così silenziosamente sulla stessa matematica di quella discreta, un livello più in basso di dove il docstring già avvertiva del rischio. Aggiuntosmooth_mode: bool = False(gestito conjnp.where, compatibile conjax.jit/vmap);compute_damping_gating_smoothora passasmooth_mode=True. La variante discreta di default non è cambiata. Verificato: prima del fix le due varianti davano risultati identici anche su input non interi; ora divergono, come previsto.
[1.0.6]¶
Risolti i 3 punti lasciati aperti come Known Issues in 1.0.5.
Changed¶
- Logging (comportamento cambiato, non solo interno): le ~14 chiamate
print()che stampavano lo stato diOrca.protect_and_forwardad ogni inferenza ([ORCA] Attivazione...,orca/utility/iodat.py,core/compiler.pysave/load pipeline) sono oralogging.getLogger(__name__). Di default sono silenziose (nessun handler configurato dalla libreria, convenzione standard) — per vederle di nuovo:logging.basicConfig(level=logging.INFO)prima di usare la libreria. Le ~35print()rimaste (inarmatura.pymetodoreferto()e CLImain(),utility/anwav.py,utility/diagnostic.py) non sono state toccate: sono referti/output voluti quando l'utente chiama esplicitamente quelle funzioni, non rumore di background — convertirle avrebbe reso silenzioso per default uno strumento il cui scopo e' stampare un referto.
Fixed¶
- Test JIT flaky sotto pressione di RAM:
Orca._gc_se_ram_bassa()chiamavajax.clear_caches()(svuota la cache di compilazione JIT di TUTTO il processo) alla stessa soglia morbida digc.collect()— se la RAM libera scendeva anche solo temporaneamente sotto quel margine preventivo, la precompilazione fatta in__init__veniva vanificata e ogni chiamata successiva ricompilava XLA da zero. Orajax.clear_caches()scatta solo al limite duro (min_free_ram), non al margine preventivo. Il test di regressione (test_orca_protect_and_forward_usa_la_cache_jit_ non_ricompila_ogni_volta) ora finge anche RAM abbondante via mock, rendendolo indipendente dallo stato reale della macchina/CI. - Copertura docstring/type hint: dal 53.9%/58.8% (55/60 su 102 funzioni) al 100% — tutte le funzioni/metodi pubblici e privati hanno ora una docstring one-line e annotazioni di tipo su argomenti/ritorno.
except Exception:generico: ristretto a eccezioni specifiche dove identificabili —core/memory.py(subprocess/parsing dinvidia-smia(CalledProcessError, TimeoutExpired, FileNotFoundError, OSError, ValueError); query VRAMjax.devices()a(RuntimeError, AttributeError)),core/noise.py(jax.default_backend()aRuntimeError). Lasciati broad-by-design, ma documentati con un commento:memory.py(pulizia cache JIT best-effort, ora anche loggata a livello debug invece dipasssilenzioso) eutility/resonance_search.py::smoke_test(per definizione uno smoke test deve catturare qualunque fallimento).
[1.0.5]¶
Fixed¶
pyproject.toml: campolicensecome tabella TOML deprecata da setuptools (avviso ad ogni build, sarebbe diventato errore bloccante dal 18 febbraio 2027) sostituito con stringa SPDX standard"BUSL-1.1"+license-files = ["LICENSE.md"]. Richiedesetuptools>=77(bumpato inbuild-system.requires).- Residuo del vecchio nome del progetto ("sentinel02") nei commenti di
intestazione di
dense_armor/core/damping_operator.pyedense_armor/utility/metro.py— aggiornati al percorso corrente (shield_/...), nessun impatto funzionale.
Known Issues (non risolti in questa versione)¶
- Logging: 49 chiamate a
print()sparse nel codice per i messaggi[ORCA] .... Esiste gia'dense_armor/core/logger.pyma non e' importato da nessun altro modulo — chi integra la libreria non puo' abbassare la verbosita', reindirizzare su file o silenziare i log senza modificare il sorgente. - Copertura docstring/type hint: su 102 funzioni/metodi totali, 55 hanno una docstring (53.9%) e 60 hanno almeno un type hint tra argomenti/valore di ritorno (58.8%).
except Exception:generico: 5 punti che catturano tutto silenziosamente —core/memory.py(righe 56, 72, 96),core/noise.py(riga 43),utility/resonance_search.py(riga 73). Da rivedere caso per caso: quali eccezioni sono davvero previste (es. file mancante) e quali nascondono bug che dovrebbero propagarsi.
[1.0.4]¶
Changed¶
- Nessuna modifica al codice rispetto a 1.0.3 — solo bump di versione, perche' 1.0.3 risultava gia' occupata su PyPI da un caricamento precedente.
[1.0.3]¶
Fixed¶
filter_data_stream(motore diAdaptiveSignalStabilizer) ricompilava l'intero kernel JIT ad ogni chiamata invece di riusare quello precompilato — kernel 1D spostato in__init__, soglie/gain passati come argomenti jit invece che letti daself.*dentro la funzione.jnp.insertchiamato fuori dajax.jitsubito dopo lo scan (stesso problema, punto diverso) — sostituito connp.insertpost-conversione.Orca._execute_4_phase_input_shield/_execute_4_phase_output_shieldeseguivano la loro pipeline elementwise (jnp.where/jnp.isnan/ aritmetica) in modalita' eager, fuori da qualunquejax.jit— estratta in due kernel dedicati precompilati una sola volta in__init__. Eliminate anche chiamate ridondanti duplicate afilter_batch_scenarios/compute_damping_gating.- Risultato: 3 chiamate a
protect_and_forwardpassano da ~11s a ~0.03s (zero ricompilazioni XLA residue a regime, verificato concProfile). - CI: workflow lanciava ancora
pytest tests/dopo la rinomina della cartella intest/— corretto in.github/workflows/tests.ymleREADME.md.
Changed¶
- Cartella dei test rinominata da
tests/atest/.
[1.0.1]¶
Fixed¶
- Bug critico:
evaluate_abc_discrepancy(radicale ABCollatz) calcolava il radicale sua*b*cinvece che sul valore generato dalla traiettoria di Collatz — la traiettoria era di fatto ignorata, producendo lo stesso risultato indipendentemente dal valore Collatz reale (verificato: b=7 e b=999999 davano discrepanze identiche prima del fix). - Dipendenza core mancante
psutilaggiunta apyproject.toml(trovata testando l'installazione del wheel in un venv pulito).
Added¶
- Suite di test pytest (
test/) e CI GitHub Actions (.github/workflows/tests.yml), matrice Python 3.10/3.11/3.12 su ubuntu/windows + controllo build/metadata. - Istruzioni per lanciare la suite di test in locale nel README.
[1.0.0]¶
Prima pubblicazione.